From a4dee7bd9bde07b55a6b35468eda7980f127f5ea Mon Sep 17 00:00:00 2001 From: Leon Liu Date: Wed, 25 Dec 2024 04:04:17 +0900 Subject: [PATCH] Update. --- Cargo.lock | 61 ++++++++++++++ Cargo.toml | 4 +- src/components/app.rs | 39 ++++++--- src/components/mod.rs | 7 +- src/components/rule_detail.rs | 0 src/components/rule_item.rs | 49 ++++++++++++ src/components/rule_list.rs | 64 +++++++++++++++ src/main.rs | 55 ++++++++++--- src/models/base.rs | 2 +- src/models/filter_text.rs | 147 +++++++++++++++++++++++++--------- src/models/loot_filter.rs | 116 ++++++++++++++++++++------- 11 files changed, 446 insertions(+), 98 deletions(-) create mode 100644 src/components/rule_detail.rs create mode 100644 src/components/rule_item.rs create mode 100644 src/components/rule_list.rs diff --git a/Cargo.lock b/Cargo.lock index e4d9476..48572b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 5bea030..ef56901 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/src/components/app.rs b/src/components/app.rs index b0c014a..52a4dd2 100644 --- a/src/components/app.rs +++ b/src/components/app.rs @@ -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 { - 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 { + 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 { + match msg { + Message::RuleList(msg) => { + rule_list::update(&mut state.filter_config, msg).map(Message::RuleList) + } } } diff --git a/src/components/mod.rs b/src/components/mod.rs index 0e8caea..4c50114 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,3 +1,4 @@ -mod app; - -pub use app::*; \ No newline at end of file +pub mod app; +mod rule_detail; +mod rule_item; +mod rule_list; diff --git a/src/components/rule_detail.rs b/src/components/rule_detail.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/components/rule_item.rs b/src/components/rule_item.rs new file mode 100644 index 0000000..494035a --- /dev/null +++ b/src/components/rule_item.rs @@ -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 { + 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 { + match msg { + Message::TogglerToggled(toggled) => { + state.enabled = toggled; + Task::none() + } + Message::Delete => todo!(), + Message::MoveUp => todo!(), + Message::MoveDown => todo!(), + } +} diff --git a/src/components/rule_list.rs b/src/components/rule_list.rs new file mode 100644 index 0000000..e275793 --- /dev/null +++ b/src/components/rule_list.rs @@ -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 { + 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 { + 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() + } + } + } +} diff --git a/src/main.rs b/src/main.rs index a67640c..fdf14f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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::().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::() + .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())) -} \ No newline at end of file + .run_with(move || { + ( + App { + settings, + filter_config, + }, + Task::none(), + ) + }) +} diff --git a/src/models/base.rs b/src/models/base.rs index 69987c2..7801c33 100644 --- a/src/models/base.rs +++ b/src/models/base.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct RangedNumber(u32); impl RangedNumber { diff --git a/src/models/filter_text.rs b/src/models/filter_text.rs index 7f0db2b..f8307b6 100644 --- a/src/models/filter_text.rs +++ b/src/models/filter_text.rs @@ -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::>().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::>() + .join("\n\n") + } +} + +impl FilterRule { + fn ungroup(&self) -> Vec { + 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::>().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::>().join(" ")), - Line::BaseType(item_base_type) => format!("BaseType {}", item_base_type.iter().map(|item| format!(r#""{}""#, item.value())).collect::>().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::>() + .join(" ") + ), + Line::BaseType(item_base_type) => format!( + "BaseType {}", + item_base_type + .iter() + .map(|item| format!(r#""{}""#, item.value())) + .collect::>() + .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) - } -} \ No newline at end of file + fn to_filter_text(&self) -> String { + format!("{} {} {} {}", self.r, self.g, self.b, self.a) + } +} diff --git a/src/models/loot_filter.rs b/src/models/loot_filter.rs index 3de047e..7c62bf4 100644 --- a/src/models/loot_filter.rs +++ b/src/models/loot_filter.rs @@ -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, - 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, + 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, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct FilterGroup { + pub lines: HashMap, + pub filters: Vec, +} + +#[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), BaseType(Vec), @@ -68,45 +101,54 @@ pub struct Settings { impl Settings { fn format_err(value: &str, category: &str, options: &Vec) -> 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 + } }