This commit is contained in:
Leon Liu 2024-12-25 04:04:17 +09:00
parent 4d092d56a5
commit a4dee7bd9b
11 changed files with 446 additions and 98 deletions

61
Cargo.lock generated
View File

@ -978,6 +978,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "erased-serde"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "errno"
version = "0.3.10"
@ -1644,6 +1654,15 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "inventory"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d80fade88dd420ce0d9ab6f7c58ef2272dde38db874657950f827d4982c817"
dependencies = [
"rustversion",
]
[[package]]
name = "itertools"
version = "0.13.0"
@ -2516,6 +2535,8 @@ dependencies = [
"serde",
"serde_json",
"strum_macros",
"typetag",
"uuid",
]
[[package]]
@ -3353,12 +3374,42 @@ version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
[[package]]
name = "typeid"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e"
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "typetag"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "044fc3365ddd307c297fe0fe7b2e70588cdab4d0f62dc52055ca0d11b174cf0e"
dependencies = [
"erased-serde",
"inventory",
"once_cell",
"serde",
"typetag-impl",
]
[[package]]
name = "typetag-impl"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9d30226ac9cbd2d1ff775f74e8febdab985dab14fb14aa2582c29a92d5555dc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.91",
]
[[package]]
name = "ucd-trie"
version = "0.1.7"
@ -3436,6 +3487,16 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "uuid"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
dependencies = [
"getrandom",
"serde",
]
[[package]]
name = "version_check"
version = "0.9.5"

View File

@ -5,8 +5,10 @@ edition = "2021"
[dependencies]
config = "0.15.4"
iced = "0.13.1"
iced = { version = "0.13.1", features = ["debug"] }
itertools = "0.13.0"
serde = { version = "1.0.216", features = ["derive"] }
serde_json = "1.0.134"
strum_macros = "0.26.4"
typetag = "0.2.19"
uuid = { version = "1.11.0", features = ["v4", "serde"] }

View File

@ -1,23 +1,38 @@
use iced::widget::{container, text, Container, column};
use iced::{
widget::{button, column, container, horizontal_rule, row, text, toggler, vertical_rule},
Element, Task,
};
use crate::models::{FilterConfig, FilterText, Settings};
use crate::models::{Filter, FilterConfig, FilterRule, FilterText, Settings};
use super::rule_list;
pub struct App {
pub settings: Settings,
pub filter_config: FilterConfig
pub settings: Settings,
pub filter_config: FilterConfig,
}
#[derive(Debug, Clone, Copy)]
pub enum Message {
RuleList(rule_list::Message),
}
impl App {
pub fn view(&self) -> Container<Message> {
container(column![
text(format!("{:?}", &self.settings)),
text(format!("{:?}", &self.filter_config.to_filter_text()))
]).padding(8)
}
pub fn update(&mut self, message: Message) {
pub fn view(state: &App) -> Element<Message> {
container(
row![
rule_list::view(&state.filter_config).map(Message::RuleList),
vertical_rule(2),
column![]
]
.spacing(8),
)
.padding(8)
.into()
}
pub fn update(state: &mut App, msg: Message) -> Task<Message> {
match msg {
Message::RuleList(msg) => {
rule_list::update(&mut state.filter_config, msg).map(Message::RuleList)
}
}
}

View File

@ -1,3 +1,4 @@
mod app;
pub use app::*;
pub mod app;
mod rule_detail;
mod rule_item;
mod rule_list;

View File

View File

@ -0,0 +1,49 @@
use iced::{
widget::{button, row, text, toggler},
Element, Task,
};
use crate::models::{Filter, FilterRule, FilterText};
#[derive(Debug, Clone, Copy)]
pub enum Message {
TogglerToggled(bool),
Delete,
MoveUp,
MoveDown,
}
pub fn view(state: &Filter) -> Element<Message> {
match &state.rule {
FilterRule::Leaf(_) => row![
button(text("x").size(10))
.padding(4)
.on_press(Message::Delete),
button(text("").size(10))
.padding(4)
.on_press(Message::MoveUp),
button(text("").size(10))
.padding(4)
.on_press(Message::MoveDown),
toggler(state.enabled)
.label("")
.on_toggle(Message::TogglerToggled),
text(format!("{}", state.to_filter_text().replace("\n", " ")))
]
.spacing(4),
FilterRule::Group(group) => todo!(),
}
.into()
}
pub fn update(state: &mut Filter, msg: Message) -> Task<Message> {
match msg {
Message::TogglerToggled(toggled) => {
state.enabled = toggled;
Task::none()
}
Message::Delete => todo!(),
Message::MoveUp => todo!(),
Message::MoveDown => todo!(),
}
}

View File

@ -0,0 +1,64 @@
use iced::{
widget::{button, column, keyed_column},
Element, Task,
};
use uuid::Uuid;
use crate::models::{Filter, FilterConfig};
use super::rule_item;
#[derive(Debug, Clone, Copy)]
pub enum Message {
AddRule,
RuleItem(Uuid, rule_item::Message),
}
pub fn view(state: &FilterConfig) -> Element<Message> {
let rows = keyed_column(state.filters.iter().map(|filter| {
(
filter.id,
rule_item::view(filter).map(|msg| Message::RuleItem(filter.id, msg)),
)
}))
.spacing(4);
column![button("Add Rule").on_press(Message::AddRule), rows]
.spacing(4)
.into()
}
pub fn update(state: &mut FilterConfig, msg: Message) -> Task<Message> {
match msg {
Message::AddRule => {
state.filters.push(Filter::default_leaf());
Task::none()
}
Message::RuleItem(uuid, rule_item::Message::Delete) => {
state.filters.retain(|f| f.id != uuid);
Task::none()
}
Message::RuleItem(uuid, rule_item::Message::MoveUp) => {
if let Some(index) = state.filters.iter().position(|f| f.id == uuid) {
if index > 0 {
state.filters.swap(index, index - 1);
}
}
Task::none()
}
Message::RuleItem(uuid, rule_item::Message::MoveDown) => {
if let Some(index) = state.filters.iter().position(|f| f.id == uuid) {
if index < state.filters.len() - 1 {
state.filters.swap(index, index + 1);
}
}
Task::none()
}
Message::RuleItem(uuid, msg) => {
if let Some(filter) = state.filters.iter_mut().find(|filter| filter.id == uuid) {
rule_item::update(filter, msg).map(move |msg| Message::RuleItem(uuid, msg))
} else {
Task::none()
}
}
}
}

View File

@ -1,21 +1,52 @@
mod models;
mod components;
mod models;
use components::App;
use iced::{Size, Task, Theme};
use models::Settings;
use std::collections::HashMap;
use components::app::App;
use config::Config;
use iced::{Size, Task, Theme};
use models::{Filter, FilterConfig, FilterLeaf, FilterRule, Level, Line, Settings};
use uuid::Uuid;
pub fn main() -> iced::Result {
let settings = Config::builder()
.add_source(config::File::with_name("config/settings.json"))
.build().unwrap().try_deserialize::<Settings>().unwrap();
iced::application("POE2 Loot Filter Config Tools", App::update, App::view)
.add_source(config::File::with_name("config/settings.json"))
.build()
.unwrap()
.try_deserialize::<Settings>()
.unwrap();
let mut lines = HashMap::new();
lines.insert(
"k".to_string(),
Line::AreaLevel(
settings.op(&"<".to_string()).unwrap(),
Level::new(80).unwrap(),
),
);
let filter_config = FilterConfig {
filters: vec![Filter {
id: Uuid::new_v4(),
enabled: true,
rule: FilterRule::Leaf(FilterLeaf { show: true, lines }),
}],
};
iced::application(
"POE2 Loot Filter Config",
components::app::update,
components::app::view,
)
.theme(|_| Theme::GruvboxDark)
.window_size(Size::new(1920.0 / 2., 1080.0 / 2.))
.centered()
.run_with(|| (App {
settings: settings,
filter_config: vec![]
}, Task::none()))
}
.run_with(move || {
(
App {
settings,
filter_config,
},
Task::none(),
)
})
}

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RangedNumber<const MIN: u32, const MAX: u32>(u32);
impl<const MIN: u32, const MAX: u32> RangedNumber<MIN, MAX> {

View File

@ -1,51 +1,122 @@
use super::{Color, Filter, FilterConfig, Line};
use itertools::Itertools;
use super::{Color, Filter, FilterConfig, FilterLeaf, FilterRule, Line};
pub trait FilterText {
fn to_filter_text(&self) -> String;
fn to_filter_text(&self) -> String;
}
impl FilterText for FilterConfig {
fn to_filter_text(&self) -> String {
self.filters.iter().rev()
.map(|filter| {filter.to_filter_text()})
.collect::<Vec<_>>().join("\n\n")
}
impl<'a> FilterText for FilterConfig {
fn to_filter_text(&self) -> String {
self.filters
.iter()
.rev()
.filter(|filter| filter.enabled)
.map(|filter| filter.to_filter_text())
.collect::<Vec<_>>()
.join("\n\n")
}
}
impl FilterRule {
fn ungroup(&self) -> Vec<FilterLeaf> {
match self {
FilterRule::Leaf(leaf) => vec![leaf.clone()],
FilterRule::Group(group) => group
.filters
.iter()
.filter(|filter| filter.enabled)
.flat_map(|filter| filter.rule.ungroup())
.map(|leaf| {
let mut new_leaf = leaf.clone();
for (key, value) in &group.lines {
new_leaf.lines.insert(key.to_string(), value.clone());
}
new_leaf
})
.collect_vec(),
}
}
}
impl FilterText for FilterRule {
fn to_filter_text(&self) -> String {
self.ungroup()
.iter()
.map(|leaf| {
if leaf.show { "Show" } else { "Hide" }.to_string()
+ "\n"
+ &leaf
.lines
.values()
.map(|line| line.to_filter_text())
.join("\n")
})
.join("\n\n")
}
}
impl FilterText for Filter {
fn to_filter_text(&self) -> String {
let head = if self.show {"Show"} else {"Hide"};
head.to_owned() + "\n" + &self.lines.iter().map(|line| {line.to_filter_text()})
.collect::<Vec<_>>().join("\n")
}
fn to_filter_text(&self) -> String {
self.rule.to_filter_text()
}
}
impl FilterText for Line {
fn to_filter_text(&self) -> String {
match self {
Line::Class(item_class) => format!("Class {}", item_class.iter().map(|item| format!(r#""{}""#, item.value())).collect::<Vec<_>>().join(" ")),
Line::BaseType(item_base_type) => format!("BaseType {}", item_base_type.iter().map(|item| format!(r#""{}""#, item.value())).collect::<Vec<_>>().join(" ")),
Line::AreaLevel(op, level) => format!("AreaLevel {} {}", op.value(), level.value()),
Line::DropLevel(op, level) => format!("DropLevel {} {}", op.value(), level.value()),
Line::ItemLevel(op, level) => format!("ItemLevel {} {}", op.value(), level.value()),
Line::Rarity(op, item_rarity) => format!("Rarity {} {}", op.value(), item_rarity.value()),
Line::Sockets(op, i) => format!("Sockets {} {}", op.value(), i),
Line::Quality(op, i) => format!("Quality {} {}", op.value(), i),
Line::StackSize(op, i) => format!("StackSize {} {}", op.value(), i),
Line::WaystoneTier(op, waystone_tier) => format!("WaystoneTier {} {}", op.value(), waystone_tier.value()),
Line::SetFontSize(font_size) => format!("SetFontSize {}", font_size.value()),
Line::SetTextColor(color) => format!("SetTextColor {}", color.to_filter_text()),
Line::SetBorderColor(color) => format!("SetBorderColor {}", color.to_filter_text()),
Line::SetBackgroundColor(color) => format!("SetBackgroundColor {}", color.to_filter_text()),
Line::PlayAlertSound(alert_sound_id, alert_sound_volume) => format!("PlayAlertSound {} {}", alert_sound_id.value(), alert_sound_volume.value()),
Line::PlayEffect(game_color) => format!("PlayEffect {}", game_color.value()),
Line::MinimapIcon(minimap_icon_size, game_color, minimap_icon_shape) => format!("MinimapIcon {} {} {}", minimap_icon_size.value(), game_color.value(), minimap_icon_shape.value()),
}
}
fn to_filter_text(&self) -> String {
match self {
Line::Class(item_class) => format!(
"Class {}",
item_class
.iter()
.map(|item| format!(r#""{}""#, item.value()))
.collect::<Vec<_>>()
.join(" ")
),
Line::BaseType(item_base_type) => format!(
"BaseType {}",
item_base_type
.iter()
.map(|item| format!(r#""{}""#, item.value()))
.collect::<Vec<_>>()
.join(" ")
),
Line::AreaLevel(op, level) => format!("AreaLevel {} {}", op.value(), level.value()),
Line::DropLevel(op, level) => format!("DropLevel {} {}", op.value(), level.value()),
Line::ItemLevel(op, level) => format!("ItemLevel {} {}", op.value(), level.value()),
Line::Rarity(op, item_rarity) => {
format!("Rarity {} {}", op.value(), item_rarity.value())
}
Line::Sockets(op, i) => format!("Sockets {} {}", op.value(), i),
Line::Quality(op, i) => format!("Quality {} {}", op.value(), i),
Line::StackSize(op, i) => format!("StackSize {} {}", op.value(), i),
Line::WaystoneTier(op, waystone_tier) => {
format!("WaystoneTier {} {}", op.value(), waystone_tier.value())
}
Line::SetFontSize(font_size) => format!("SetFontSize {}", font_size.value()),
Line::SetTextColor(color) => format!("SetTextColor {}", color.to_filter_text()),
Line::SetBorderColor(color) => format!("SetBorderColor {}", color.to_filter_text()),
Line::SetBackgroundColor(color) => {
format!("SetBackgroundColor {}", color.to_filter_text())
}
Line::PlayAlertSound(alert_sound_id, alert_sound_volume) => format!(
"PlayAlertSound {} {}",
alert_sound_id.value(),
alert_sound_volume.value()
),
Line::PlayEffect(game_color) => format!("PlayEffect {}", game_color.value()),
Line::MinimapIcon(minimap_icon_size, game_color, minimap_icon_shape) => format!(
"MinimapIcon {} {} {}",
minimap_icon_size.value(),
game_color.value(),
minimap_icon_shape.value()
),
}
}
}
impl FilterText for Color {
fn to_filter_text(&self) -> String {
format!("{} {} {} {}", self.r, self.g, self.b, self.a)
}
}
fn to_filter_text(&self) -> String {
format!("{} {} {} {}", self.r, self.g, self.b, self.a)
}
}

View File

@ -1,27 +1,60 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::base::RangedNumber;
use itertools::Itertools;
#[derive(Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FilterConfig {
pub filters: Vec<Filter>,
test: dyn Test
}
trait Test: std::fmt::Debug {}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Filter {
pub show: bool,
pub lines: Vec<Line>,
pub id: uuid::Uuid,
pub enabled: bool,
pub rule: FilterRule,
}
impl Filter {
pub fn default_leaf() -> Self {
Self {
id: Uuid::new_v4(),
enabled: true,
rule: FilterRule::Leaf(FilterLeaf::default()),
}
}
#[derive(Serialize, Deserialize, Debug)]
fn default_group() -> Self {
Self {
id: Uuid::new_v4(),
enabled: true,
rule: FilterRule::Group(FilterGroup::default()),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum FilterRule {
Leaf(FilterLeaf),
Group(FilterGroup),
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct FilterLeaf {
pub show: bool,
pub lines: HashMap<String, Line>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct FilterGroup {
pub lines: HashMap<String, Line>,
pub filters: Vec<Filter>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Color {
pub r: u8,
pub g: u8,
@ -31,7 +64,7 @@ pub struct Color {
pub type Level = RangedNumber<1, 100>;
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Line {
Class(Vec<ItemClass>),
BaseType(Vec<ItemBaseType>),
@ -68,45 +101,54 @@ pub struct Settings {
impl Settings {
fn format_err(value: &str, category: &str, options: &Vec<String>) -> String {
format!("{} is not a valid {}, options are: {}", value, category, options.iter().join(" "))
format!(
"{} is not a valid {}, options are: {}",
value,
category,
options.iter().join(" ")
)
}
pub fn base_type(&self, s: &String) -> Result<ItemBaseType, String> {
if self.base_types.contains(s) {
if !self.base_types.contains(s) {
Err(Self::format_err(s, "BaseType", &self.base_types))
} else {
Ok(ItemBaseType(s.to_string()))
}
}
pub fn class(&self, s: &String) -> Result<ItemClass, String> {
if self.classes.contains(s) {
if !self.classes.contains(s) {
Err(Self::format_err(s, "Class", &self.classes))
} else {
Ok(ItemClass(s.to_string()))
}
}
pub fn rarity(&self, s: &String) -> Result<ItemRarity, String> {
if self.rarities.contains(s) {
if !self.rarities.contains(s) {
Err(Self::format_err(s, "Rarity", &self.rarities))
} else {
Ok(ItemRarity(s.to_string()))
}
}
pub fn op(&self, s: &String) -> Result<Op, String> {
if self.ops.contains(s) {
if !self.ops.contains(s) {
Err(Self::format_err(s, "Operator", &self.ops))
} else {
Ok(Op(s.to_string()))
}
}
pub fn minimap_icon_shape(&self, s: &String) -> Result<MinimapIconShape, String> {
if self.minimap_icon_shapes.contains(s) {
Err(Self::format_err(s, "MinimapIconShape", &self.minimap_icon_shapes))
if !self.minimap_icon_shapes.contains(s) {
Err(Self::format_err(
s,
"MinimapIconShape",
&self.minimap_icon_shapes,
))
} else {
Ok(MinimapIconShape(s.to_string()))
}
}
pub fn game_color(&self, s: &String) -> Result<GameColor, String> {
if self.game_colors.contains(s) {
if !self.game_colors.contains(s) {
Err(Self::format_err(s, "GameColor", &self.game_colors))
} else {
Ok(GameColor(s.to_string()))
@ -114,39 +156,51 @@ impl Settings {
}
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ItemBaseType(String);
impl ItemBaseType {
pub fn value(&self) -> &str {&self.0}
pub fn value(&self) -> &str {
&self.0
}
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ItemClass(String);
impl ItemClass {
pub fn value(&self) -> &str {&self.0}
pub fn value(&self) -> &str {
&self.0
}
}
#[derive(Deserialize, Serialize, Debug)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ItemRarity(String);
impl ItemRarity {
pub fn value(&self) -> &str {&self.0}
pub fn value(&self) -> &str {
&self.0
}
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MinimapIconShape(String);
impl MinimapIconShape {
pub fn value(&self) -> &str {&self.0}
pub fn value(&self) -> &str {
&self.0
}
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GameColor(String);
impl GameColor {
pub fn value(&self) -> &str {&self.0}
pub fn value(&self) -> &str {
&self.0
}
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Op(String);
impl Op {
pub fn value(&self) -> &str {&self.0}
pub fn value(&self) -> &str {
&self.0
}
}