diff --git a/Cargo.lock b/Cargo.lock index 19f22d81..9759b3eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,6 +167,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "allocator-api2" version = "0.2.16" @@ -3210,6 +3216,7 @@ dependencies = [ "luminol-components", "luminol-core", "luminol-data", + "ouroboros", ] [[package]] @@ -3881,6 +3888,31 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "ouroboros" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" +dependencies = [ + "heck", + "itertools 0.12.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.51", +] + [[package]] name = "overload" version = "0.1.1" @@ -4184,6 +4216,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.51", + "version_check", + "yansi", +] + [[package]] name = "profiling" version = "1.0.15" @@ -6645,6 +6690,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zbus" version = "3.15.2" diff --git a/crates/data/src/shared/event.rs b/crates/data/src/shared/event.rs index d835ef5b..4969ac20 100644 --- a/crates/data/src/shared/event.rs +++ b/crates/data/src/shared/event.rs @@ -224,6 +224,8 @@ pub struct EventCondition { #[serde(with = "id_serde")] #[marshal(with = "id_alox")] pub switch2_id: usize, + #[serde(with = "id_serde")] + #[marshal(with = "id_alox")] pub variable_id: usize, pub variable_value: i32, pub self_switch_ch: SelfSwitch, diff --git a/crates/modals/Cargo.toml b/crates/modals/Cargo.toml index 8262cbb4..a575e5a3 100644 --- a/crates/modals/Cargo.toml +++ b/crates/modals/Cargo.toml @@ -26,3 +26,5 @@ luminol-data.workspace = true luminol-components.workspace = true fuzzy-matcher = "0.3.7" + +ouroboros = "0.18.4" diff --git a/crates/modals/src/database_modal/mod.rs b/crates/modals/src/database_modal/mod.rs new file mode 100644 index 00000000..4435e14c --- /dev/null +++ b/crates/modals/src/database_modal/mod.rs @@ -0,0 +1,218 @@ +// Copyright (C) 2024 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +// +// Additional permission under GNU GPL version 3 section 7 +// +// If you modify this Program, or any covered work, by linking or combining +// it with Steamworks API by Valve Corporation, containing parts covered by +// terms of the Steamworks API by Valve Corporation, the licensors of this +// Program grant you additional permission to convey the resulting work. + +use std::marker::PhantomData; + +use luminol_components::UiExt; + +mod variable; +pub use variable::Variable; +mod switch; +pub use switch::Switch; + +pub struct Modal { + state: State, + id: egui::Id, + _phantom: PhantomData, // so that M is constrained +} + +enum State { + Closed, + Open { + search_text: String, + selected_id: usize, + new_size: Option, + }, +} + +#[allow(unused_variables)] +pub trait DatabaseModalHandler { + fn window_title() -> &'static str; + + fn button_format(id: &mut usize, update_state: &mut luminol_core::UpdateState<'_>) -> String; + + fn iter( + update_state: &mut luminol_core::UpdateState<'_>, + f: impl FnOnce(&mut dyn Iterator), // can't figure out how to avoid the dyn + ); + + fn current_size(update_state: &luminol_core::UpdateState<'_>) -> Option { + None + } + fn resize(update_state: &mut luminol_core::UpdateState<'_>, new_size: usize) {} +} + +impl Modal +where + M: DatabaseModalHandler, +{ + pub fn new(id: egui::Id) -> Self { + Self { + state: State::Closed, + id, + _phantom: PhantomData, + } + } +} + +impl luminol_core::Modal for Modal +where + M: DatabaseModalHandler, +{ + type Data = usize; + + fn button<'m>( + &'m mut self, + data: &'m mut Self::Data, + update_state: &'m mut luminol_core::UpdateState<'_>, + ) -> impl egui::Widget + 'm { + move |ui: &mut egui::Ui| { + let button_text = if ui.is_enabled() { + M::button_format(data, update_state) + } else { + "...".to_string() + }; + let button_response = ui.button(button_text); + + if button_response.clicked() { + self.state = State::Open { + search_text: String::new(), + selected_id: *data, + new_size: M::current_size(update_state), + }; + } + if ui.is_enabled() { + self.show_window(ui.ctx(), data, update_state); + } + + button_response + } + } + + fn reset(&mut self) { + self.state = State::Closed; + } +} + +impl Modal +where + M: DatabaseModalHandler, +{ + fn show_window( + &mut self, + ctx: &egui::Context, + data: &mut usize, + update_state: &mut luminol_core::UpdateState<'_>, + ) { + let mut win_open = true; + let mut keep_open = true; + let mut needs_save = false; + + let State::Open { + search_text, + selected_id, + new_size, + } = &mut self.state + else { + return; + }; + + egui::Window::new(M::window_title()) + .resizable(true) + .open(&mut win_open) + .id(self.id) + .show(ctx, |ui| { + let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); + + ui.group(|ui| { + egui::ScrollArea::vertical() + .auto_shrink([false, false]) + .max_height(384.) + .show(ui, |ui| { + M::iter(update_state, |iter| { + for (id, text) in iter { + if matcher.fuzzy(&text, search_text, false).is_none() { + continue; + } + + ui.with_stripe(id % 2 == 0, |ui| { + ui.horizontal(|ui| { + let response = + ui.selectable_value(selected_id, id, text); + ui.add_space(ui.available_width()); + if response.double_clicked() { + keep_open = false; + needs_save = true; + } + }); + }); + } + }) + }) + }); + + if M::current_size(update_state).is_some_and(|size| size <= 999) && new_size.is_some_and(|size| size > 999) { + egui::Frame::none().show(ui, |ui| { + ui.style_mut() + .visuals + .widgets + .noninteractive + .bg_stroke + .color = ui.style().visuals.warn_fg_color; + egui::Frame::group(ui.style()) + .fill(ui.visuals().gray_out(ui.visuals().gray_out( + ui.visuals().gray_out(ui.style().visuals.warn_fg_color), + ))) + .show(ui, |ui| { + ui.set_width(ui.available_width()); + ui.label(egui::RichText::new("Setting the maximum above 999 may introduce performance issues and instability").color(ui.style().visuals.warn_fg_color)); + }); + }); + } + + ui.horizontal(|ui| { + luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + + if let Some(size) = new_size { + ui.add(egui::DragValue::new(size).clamp_range(1..=usize::MAX)); + if ui.button("Set Maximum").clicked() { + M::resize(update_state, *size); + } + } + + egui::TextEdit::singleline(search_text) + .hint_text("Search 🔎") + .show(ui); + }); + }); + + if needs_save { + *data = *selected_id; + } + + if !win_open || !keep_open { + self.state = State::Closed; + } + } +} diff --git a/crates/modals/src/database_modal/switch.rs b/crates/modals/src/database_modal/switch.rs new file mode 100644 index 00000000..6da9bc69 --- /dev/null +++ b/crates/modals/src/database_modal/switch.rs @@ -0,0 +1,59 @@ +// Copyright (C) 2024 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +// +// Additional permission under GNU GPL version 3 section 7 +// +// If you modify this Program, or any covered work, by linking or combining +// it with Steamworks API by Valve Corporation, containing parts covered by +// terms of the Steamworks API by Valve Corporation, the licensors of this +// Program grant you additional permission to convey the resulting work. + +pub struct Switch; + +impl super::DatabaseModalHandler for Switch { + fn button_format(id: &mut usize, update_state: &mut luminol_core::UpdateState<'_>) -> String { + let system = update_state.data.system(); + *id = system.switches.len().min(*id); + format!("{:0>3}: {}", *id + 1, system.switches[*id]) + } + + fn window_title() -> &'static str { + "Switches" + } + + fn iter( + update_state: &mut luminol_core::UpdateState<'_>, + f: impl FnOnce(&mut dyn Iterator), + ) { + let system = update_state.data.system(); + let mut iter = system + .switches + .iter() + .enumerate() + .map(|(id, name)| (id, format!("{:0>3}: {name}", id + 1))); + f(&mut iter); + } + + fn current_size(update_state: &luminol_core::UpdateState<'_>) -> Option { + Some(update_state.data.system().variables.len()) + } + + fn resize(update_state: &mut luminol_core::UpdateState<'_>, new_size: usize) { + let system = &mut update_state.data.system(); + system.variables.resize_with(new_size, String::new); + } +} diff --git a/crates/modals/src/database_modal/variable.rs b/crates/modals/src/database_modal/variable.rs new file mode 100644 index 00000000..29300f48 --- /dev/null +++ b/crates/modals/src/database_modal/variable.rs @@ -0,0 +1,59 @@ +// Copyright (C) 2024 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . +// +// Additional permission under GNU GPL version 3 section 7 +// +// If you modify this Program, or any covered work, by linking or combining +// it with Steamworks API by Valve Corporation, containing parts covered by +// terms of the Steamworks API by Valve Corporation, the licensors of this +// Program grant you additional permission to convey the resulting work. + +pub struct Variable; + +impl super::DatabaseModalHandler for Variable { + fn button_format(id: &mut usize, update_state: &mut luminol_core::UpdateState<'_>) -> String { + let system = update_state.data.system(); + *id = system.variables.len().min(*id); + format!("{:0>3}: {}", *id + 1, system.variables[*id]) + } + + fn window_title() -> &'static str { + "Variables" + } + + fn iter( + update_state: &mut luminol_core::UpdateState<'_>, + f: impl FnOnce(&mut dyn Iterator), + ) { + let system = update_state.data.system(); + let mut iter = system + .variables + .iter() + .enumerate() + .map(|(id, name)| (id, format!("{:0>3}: {name}", id + 1))); + f(&mut iter); + } + + fn current_size(update_state: &luminol_core::UpdateState<'_>) -> Option { + Some(update_state.data.system().variables.len()) + } + + fn resize(update_state: &mut luminol_core::UpdateState<'_>, new_size: usize) { + let system = &mut update_state.data.system(); + system.variables.resize_with(new_size, String::new); + } +} diff --git a/crates/modals/src/lib.rs b/crates/modals/src/lib.rs index a6bd6c85..75d0d625 100644 --- a/crates/modals/src/lib.rs +++ b/crates/modals/src/lib.rs @@ -30,3 +30,5 @@ pub mod variable; pub mod sound_picker; pub mod graphic_picker; + +pub mod database_modal; diff --git a/crates/modals/src/switch.rs b/crates/modals/src/switch.rs index 2cdf8d2c..1ef7ad1c 100644 --- a/crates/modals/src/switch.rs +++ b/crates/modals/src/switch.rs @@ -22,130 +22,4 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. -use luminol_components::UiExt; - -pub struct Modal { - state: State, - id: egui::Id, -} - -enum State { - Closed, - Open { - search_text: String, - switch_id: usize, - }, -} - -impl Modal { - pub fn new(id: egui::Id) -> Self { - Self { - state: State::Closed, - id, - } - } -} - -impl luminol_core::Modal for Modal { - type Data = usize; - - fn button<'m>( - &'m mut self, - data: &'m mut Self::Data, - update_state: &'m mut luminol_core::UpdateState<'_>, - ) -> impl egui::Widget + 'm { - move |ui: &mut egui::Ui| { - let button_text = if ui.is_enabled() { - let system = update_state.data.system(); - *data = system.switches.len().min(*data); - format!("{:0>3}: {}", *data + 1, system.switches[*data]) - } else { - "...".to_string() - }; - let button_response = ui.button(button_text); - - if button_response.clicked() { - self.state = State::Open { - search_text: String::new(), - switch_id: *data, - }; - } - if ui.is_enabled() { - self.show_window(ui.ctx(), data, update_state); - } - - button_response - } - } - - fn reset(&mut self) { - self.state = State::Closed; - } -} - -impl Modal { - fn show_window( - &mut self, - ctx: &egui::Context, - data: &mut usize, - update_state: &mut luminol_core::UpdateState<'_>, - ) { - let mut win_open = true; - let mut keep_open = true; - let mut needs_save = false; - - let State::Open { - search_text, - switch_id, - } = &mut self.state - else { - return; - }; - - egui::Window::new("Switch Picker") - .resizable(false) - .open(&mut win_open) - .id(self.id) - .show(ctx, |ui| { - let system = update_state.data.system(); - - let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); - - ui.group(|ui| { - egui::ScrollArea::vertical() - .auto_shrink([false, false]) - .max_height(384.) - .show(ui, |ui| { - for (id, name) in system.switches.iter().enumerate() { - let text = format!("{:0>3}: {name}", id + 1); - if matcher.fuzzy(&text, search_text, false).is_none() { - continue; - } - ui.with_stripe(id % 2 == 0, |ui| { - let response = ui.selectable_value(switch_id, id, text); - if response.double_clicked() { - keep_open = false; - needs_save = true; - } - }); - } - }) - }); - ui.horizontal(|ui| { - luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); - - egui::TextEdit::singleline(search_text) - .hint_text("Search 🔎") - .show(ui); - }); - }); - - if needs_save { - *data = *switch_id; - } - - if !win_open || !keep_open { - self.state = State::Closed; - } - } -} +pub type Modal = crate::database_modal::Modal; diff --git a/crates/modals/src/variable.rs b/crates/modals/src/variable.rs index 96cd019a..2eb87471 100644 --- a/crates/modals/src/variable.rs +++ b/crates/modals/src/variable.rs @@ -22,133 +22,5 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. -use luminol_components::UiExt; - -// TODO generalize modal into id modal -pub struct Modal { - state: State, - id: egui::Id, -} - -enum State { - Closed, - Open { - search_text: String, - variable_id: usize, - }, -} - -impl Modal { - pub fn new(id: egui::Id) -> Self { - Self { - state: State::Closed, - id, - } - } -} - -impl luminol_core::Modal for Modal { - type Data = usize; - - fn button<'m>( - &'m mut self, - data: &'m mut Self::Data, - update_state: &'m mut luminol_core::UpdateState<'_>, - ) -> impl egui::Widget { - move |ui: &mut egui::Ui| { - let button_text = if ui.is_enabled() { - let system = update_state.data.system(); - *data = system.variables.len().min(*data); - format!("{:0>3}: {}", *data + 1, system.variables[*data]) - } else { - "...".to_string() - }; - let button_response = ui.button(button_text); - - if button_response.clicked() { - self.state = State::Open { - search_text: "".to_string(), - variable_id: *data, - }; - } - if ui.is_enabled() { - self.show_window(ui.ctx(), data, update_state); - } - - button_response - } - } - - fn reset(&mut self) { - self.state = State::Closed; - } -} - -impl Modal { - fn show_window( - &mut self, - ctx: &egui::Context, - data: &mut usize, - update_state: &mut luminol_core::UpdateState<'_>, - ) { - let mut win_open = true; - let mut keep_open = true; - let mut needs_save = false; - - let State::Open { - search_text, - variable_id, - } = &mut self.state - else { - return; - }; - - egui::Window::new("Variable Picker") - .resizable(false) - .open(&mut win_open) - .id(self.id) - .show(ctx, |ui| { - let system = update_state.data.system(); - - let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); - - ui.group(|ui| { - egui::ScrollArea::vertical() - .auto_shrink([false, false]) - .max_height(384.) - .show(ui, |ui| { - for (id, name) in system.variables.iter().enumerate() { - let text = format!("{:0>3}: {name}", id + 1); - if matcher.fuzzy(&text, search_text, false).is_none() { - continue; - } - - ui.with_stripe(id % 2 == 0, |ui| { - let response = ui.selectable_value(variable_id, id, text); - if response.double_clicked() { - keep_open = false; - needs_save = true; - } - }); - } - }) - }); - - ui.horizontal(|ui| { - luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); - - egui::TextEdit::singleline(search_text) - .hint_text("Search 🔎") - .show(ui); - }); - }); - - if needs_save { - *data = *variable_id; - } - - if !win_open || !keep_open { - self.state = State::Closed; - } - } -} +// kinda a hack so we dont need to rewrite code +pub type Modal = crate::database_modal::Modal;