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;