From 96ea4b54ac1e6b75e6d53c1d9cfc6ece133dd5c0 Mon Sep 17 00:00:00 2001 From: Speak2Erase Date: Sun, 16 Jun 2024 12:53:42 -0700 Subject: [PATCH 01/67] forget outputstream, making audio send+sync --- crates/audio/src/lib.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/crates/audio/src/lib.rs b/crates/audio/src/lib.rs index 5e75ee4e..967028e9 100644 --- a/crates/audio/src/lib.rs +++ b/crates/audio/src/lib.rs @@ -41,9 +41,6 @@ pub struct Audio { } struct Inner { - // OutputStream is lazily evaluated specifically for wasm. web prevents autoplay without user interaction, this is a way of dealing with that. - // To actually play tracks the user will have needed to interact with the ui. - _output_stream: rodio::OutputStream, output_stream_handle: rodio::OutputStreamHandle, sinks: std::collections::HashMap, } @@ -59,13 +56,6 @@ pub enum Source { SE, } -#[cfg(not(target_arch = "wasm32"))] // Audio can't be shared between threads in wasm either -/// # Safety -/// cpal claims that Stream (which is why Inner is not send) is not thread safe on android, which is why it is not Send anywhere else. -/// We don't support android. The only other solution would be to use thread_local and... no. -#[allow(unsafe_code)] -unsafe impl Send for Inner {} - impl Default for Audio { fn default() -> Self { #[cfg(target_arch = "wasm32")] @@ -74,9 +64,9 @@ impl Default for Audio { } let (output_stream, output_stream_handle) = rodio::OutputStream::try_default().unwrap(); + std::mem::forget(output_stream); // Prevent the stream from being dropped Self { inner: parking_lot::Mutex::new(Inner { - _output_stream: output_stream, output_stream_handle, sinks: std::collections::HashMap::default(), }), From 8e175fd51273630f911b216a470229b064144a6d Mon Sep 17 00:00:00 2001 From: Speak2Erase Date: Sun, 16 Jun 2024 17:59:56 -0700 Subject: [PATCH 02/67] Port over modals from old branch --- Cargo.lock | 1 + crates/core/src/modal.rs | 28 +--- crates/modals/Cargo.toml | 2 + crates/modals/src/graphic_picker.rs | 20 +-- crates/modals/src/sound_picker.rs | 20 +-- crates/modals/src/switch.rs | 144 +++++++++++---------- crates/modals/src/variable.rs | 144 +++++++++++---------- crates/ui/src/windows/common_event_edit.rs | 17 +-- 8 files changed, 184 insertions(+), 192 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea500a3d..19f22d81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3206,6 +3206,7 @@ version = "0.4.0" dependencies = [ "camino", "egui", + "fuzzy-matcher", "luminol-components", "luminol-core", "luminol-data", diff --git a/crates/core/src/modal.rs b/crates/core/src/modal.rs index 64fe6fe0..fcb7a531 100644 --- a/crates/core/src/modal.rs +++ b/crates/core/src/modal.rs @@ -23,30 +23,16 @@ // Program grant you additional permission to convey the resulting work. /// A basic trait describing a modal that edits some value. -/// Modals can be open and will set their open state. -/// They should (generally) respect the open bool passed to them and only show if it is true. -/// -/// Modals are not persistent (unlike windows) and are not stored on the heap. -/// They are stored on the stack and the stack *only*. -// TODO: Make more featureful and general pub trait Modal: Sized { /// The output type for this modal. type Data; - /// Display a button to show this modal. - /// It should call show. - fn button( - this: &mut Option, - ui: &mut egui::Ui, - data: &mut Self::Data, - update_state: &mut crate::UpdateState<'_>, - ); + /// Return a widget that displays a button for this modal. + fn button<'m>( + &'m mut self, + data: &'m mut Self::Data, + update_state: &'m mut crate::UpdateState<'_>, + ) -> impl egui::Widget + 'm; // woah rpitit (so cool) - /// Show this modal. - fn show( - this: &mut Option, - ctx: &egui::Context, - data: &mut Self::Data, - update_state: &mut crate::UpdateState<'_>, - ); + fn reset(&mut self); } diff --git a/crates/modals/Cargo.toml b/crates/modals/Cargo.toml index c658d78e..8262cbb4 100644 --- a/crates/modals/Cargo.toml +++ b/crates/modals/Cargo.toml @@ -24,3 +24,5 @@ camino.workspace = true luminol-core.workspace = true luminol-data.workspace = true luminol-components.workspace = true + +fuzzy-matcher = "0.3.7" diff --git a/crates/modals/src/graphic_picker.rs b/crates/modals/src/graphic_picker.rs index a24f7628..34f3ed32 100644 --- a/crates/modals/src/graphic_picker.rs +++ b/crates/modals/src/graphic_picker.rs @@ -27,21 +27,15 @@ pub struct Modal {} impl luminol_core::Modal for Modal { type Data = camino::Utf8PathBuf; - fn button( - _this: &mut Option, - _ui: &mut egui::Ui, - _data: &mut Self::Data, - _update_state: &mut luminol_core::UpdateState<'_>, - ) { - todo!() + fn button<'m>( + &'m mut self, + data: &'m mut Self::Data, + update_state: &'m mut luminol_core::UpdateState<'_>, + ) -> impl egui::Widget + 'm { + |ui: &mut egui::Ui| todo!() } - fn show( - _this: &mut Option, - _ctx: &egui::Context, - _data: &mut Self::Data, - _update_state: &mut luminol_core::UpdateState<'_>, - ) { + fn reset(&mut self) { todo!() } } diff --git a/crates/modals/src/sound_picker.rs b/crates/modals/src/sound_picker.rs index e76a7c1d..87e7486d 100644 --- a/crates/modals/src/sound_picker.rs +++ b/crates/modals/src/sound_picker.rs @@ -29,21 +29,15 @@ pub struct Modal { impl luminol_core::Modal for Modal { type Data = luminol_data::rpg::AudioFile; - fn button( - _this: &mut Option, - _ui: &mut egui::Ui, - _data: &mut Self::Data, - _update_state: &mut luminol_core::UpdateState<'_>, - ) { - todo!() + fn button<'m>( + &'m mut self, + data: &'m mut Self::Data, + update_state: &'m mut luminol_core::UpdateState<'_>, + ) -> impl egui::Widget + 'm { + |ui: &mut egui::Ui| todo!() } - fn show( - _this: &mut Option, - _ctx: &egui::Context, - _data: &mut Self::Data, - _update_state: &mut luminol_core::UpdateState<'_>, - ) { + fn reset(&mut self) { todo!() } } diff --git a/crates/modals/src/switch.rs b/crates/modals/src/switch.rs index a6be48a2..be4e2ae0 100644 --- a/crates/modals/src/switch.rs +++ b/crates/modals/src/switch.rs @@ -22,110 +22,118 @@ // 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; + /// The switch picker modal. -pub struct Modal { - switch_id_range: std::ops::Range, - search_text: String, +#[derive(Default)] +pub enum Modal { + #[default] + Closed, + Open { + search_text: String, + switch_id: usize, + }, } impl luminol_core::Modal for Modal { type Data = usize; - fn button( - this: &mut Option, - ui: &mut egui::Ui, - data: &mut Self::Data, - update_state: &mut luminol_core::UpdateState<'_>, - ) { - let system = update_state.data.system(); - - if ui - .button(format!("{data}: {}", system.switches[*data - 1])) - .clicked() - { - this.get_or_insert(Self { - switch_id_range: *data..*data, - search_text: String::new(), - }); + 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 = Self::Open { + search_text: String::new(), + switch_id: *data, + }; + } + if ui.is_enabled() { + self.show_window(ui.ctx(), data, update_state); + } + + button_response } + } - drop(system); - - Modal::show(this, ui.ctx(), data, update_state); + fn reset(&mut self) { + *self = Self::Closed; } +} - fn show( - this_opt: &mut Option, +impl Modal { + fn show_window( + &mut self, ctx: &egui::Context, - data: &mut Self::Data, + data: &mut usize, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut win_open = this_opt.is_some(); - let mut needs_close = this_opt.is_some(); + let mut win_open = true; + let mut keep_open = true; + let mut needs_save = false; + + let Self::Open { + search_text, + switch_id, + } = self + else { + return; + }; egui::Window::new("Switch Picker") .resizable(false) .open(&mut win_open) .show(ctx, |ui| { - let this = this_opt.as_mut().unwrap(); - 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().filter(|(id, s)| { - (id + 1).to_string().contains(&this.search_text) - || s.contains(&this.search_text) - }) - { - let id = id + 1; - let mut text = egui::RichText::new(format!("{id}: {name}")); - - if this.switch_id_range.start == id { - text = text.color(egui::Color32::YELLOW); - } - - let response = ui.selectable_value(data, id, text); - - if this.switch_id_range.end == id { - this.switch_id_range.end = usize::MAX; - this.switch_id_range.start = id; - - response.scroll_to_me(None); - } - - if response.double_clicked() { - needs_close = true; + 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| { - needs_close |= ui.button("Ok").clicked(); - needs_close |= ui.button("Cancel").clicked(); + luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); - if ui - .add( - egui::DragValue::new(&mut this.switch_id_range.start) - .clamp_range(0..=system.switches.len()), - ) - .changed() - { - this.switch_id_range.end = this.switch_id_range.start; - }; - egui::TextEdit::singleline(&mut this.search_text) + egui::TextEdit::singleline(search_text) .hint_text("Search 🔎") .show(ui); }); }); - if !win_open || needs_close { - *this_opt = None; + if needs_save { + *data = *switch_id; + } + + if !win_open || !keep_open { + *self = Self::Closed; } } } diff --git a/crates/modals/src/variable.rs b/crates/modals/src/variable.rs index 07ab2588..3b6b84db 100644 --- a/crates/modals/src/variable.rs +++ b/crates/modals/src/variable.rs @@ -22,110 +22,120 @@ // 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; + +#[derive(Default)] /// The variable picker modal. -pub struct Modal { - variable_id_range: std::ops::Range, - search_text: String, +pub enum Modal { + #[default] + Closed, + Open { + search_text: String, + variable_id: usize, + }, } impl luminol_core::Modal for Modal { type Data = usize; - fn button( - this: &mut Option, - ui: &mut egui::Ui, - data: &mut Self::Data, - update_state: &mut luminol_core::UpdateState<'_>, - ) { - let system = update_state.data.system(); - - if ui - .button(format!("{data}: {}", system.variables[*data - 1])) - .clicked() - { - this.get_or_insert(Self { - variable_id_range: *data..*data, - search_text: String::new(), - }); + 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 = Self::Open { + search_text: String::new(), + variable_id: *data, + }; + } + if ui.is_enabled() { + self.show_window(ui.ctx(), data, update_state); + } + + button_response } + } - drop(system); - - Modal::show(this, ui.ctx(), data, update_state); + fn reset(&mut self) { + *self = Self::Closed; } +} - fn show( - this_opt: &mut Option, +impl Modal { + fn show_window( + &mut self, ctx: &egui::Context, - data: &mut Self::Data, + data: &mut usize, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut win_open = this_opt.is_some(); - let mut needs_close = false; + let mut win_open = true; + let mut keep_open = true; + let mut needs_save = false; + + let Self::Open { + search_text, + variable_id, + } = self + else { + return; + }; egui::Window::new("Variable Picker") .resizable(false) .open(&mut win_open) .show(ctx, |ui| { - let this = this_opt.as_mut().unwrap(); - 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().filter(|(id, s)| { - (id + 1).to_string().contains(&this.search_text) - || s.contains(&this.search_text) - }) - { - let id = id + 1; - let mut text = egui::RichText::new(format!("{id}: {name}")); - - if this.variable_id_range.start == id { - text = text.color(egui::Color32::YELLOW); - } - - let response = ui.selectable_value(data, id, text); - - if this.variable_id_range.end == id { - this.variable_id_range.end = usize::MAX; - this.variable_id_range.start = id; - - response.scroll_to_me(None); + 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; } - if response.double_clicked() { - needs_close = true; - } + 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| { - needs_close |= ui.button("Ok").clicked(); - needs_close |= ui.button("Cancel").clicked(); - - if ui - .add( - egui::DragValue::new(&mut this.variable_id_range.start) - .clamp_range(0..=system.variables.len()), - ) - .changed() - { - this.variable_id_range.end = this.variable_id_range.start; - }; - egui::TextEdit::singleline(&mut this.search_text) + luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + + egui::TextEdit::singleline(search_text) .hint_text("Search 🔎") .show(ui); }); }); - if !win_open || needs_close { - *this_opt = None; + if needs_save { + *data = *variable_id; + } + + if !win_open || !keep_open { + *self = Self::Closed; } } } diff --git a/crates/ui/src/windows/common_event_edit.rs b/crates/ui/src/windows/common_event_edit.rs index 4afcfd35..10c3de22 100644 --- a/crates/ui/src/windows/common_event_edit.rs +++ b/crates/ui/src/windows/common_event_edit.rs @@ -88,7 +88,7 @@ impl luminol_core::Window for Window { self.tabs.add_tab(CommonEventTab { event: event.clone(), force_close: false, - switch_modal: None, + switch_modal: Default::default(), command_view: luminol_components::CommandView::new( format!("common_event_{ele}"), ), @@ -111,7 +111,7 @@ impl luminol_core::Window for Window { struct CommonEventTab { event: luminol_data::rpg::CommonEvent, force_close: bool, - switch_modal: Option, + switch_modal: luminol_modals::switch::Modal, command_view: luminol_components::CommandView, } @@ -140,14 +140,11 @@ impl luminol_core::Tab for CommonEventTab { } }); - ui.add_enabled_ui(self.event.trigger > 0, |ui| { - luminol_modals::switch::Modal::button( - &mut self.switch_modal, - ui, - &mut self.event.switch_id, - update_state, - ); - }); + ui.add_enabled( + self.event.trigger > 0, + self.switch_modal + .button(&mut self.event.switch_id, update_state), + ); let mut save_event = false; From d8f83b82819dca4b411921be6d0fe6860cf38aeb Mon Sep 17 00:00:00 2001 From: Speak2Erase Date: Sun, 16 Jun 2024 22:49:45 -0700 Subject: [PATCH 03/67] add basic event editor window --- crates/components/src/lib.rs | 4 + crates/data/src/shared/event.rs | 126 +++++++++++++++++++++++--- crates/ui/src/tabs/map/mod.rs | 3 +- crates/ui/src/tabs/map/util.rs | 15 ++-- crates/ui/src/windows/event_edit.rs | 131 +++++++++++++++++++++++----- 5 files changed, 237 insertions(+), 42 deletions(-) diff --git a/crates/components/src/lib.rs b/crates/components/src/lib.rs index 8149ac42..78c3abd4 100644 --- a/crates/components/src/lib.rs +++ b/crates/components/src/lib.rs @@ -476,3 +476,7 @@ pub fn close_options_ui(ui: &mut egui::Ui, open: &mut bool, save: &mut bool) { } }); } + +pub fn colored_text(text: impl Into, color: egui::Color32) -> egui::RichText { + egui::RichText::new(text).color(color) +} diff --git a/crates/data/src/shared/event.rs b/crates/data/src/shared/event.rs index 705a1d73..d835ef5b 100644 --- a/crates/data/src/shared/event.rs +++ b/crates/data/src/shared/event.rs @@ -76,34 +76,104 @@ pub struct CommonEvent { pub struct EventPage { pub condition: EventCondition, pub graphic: Graphic, - pub move_type: usize, - pub move_speed: usize, - pub move_frequency: usize, + pub move_type: MoveType, + pub move_speed: MoveSpeed, + pub move_frequency: MoveFreq, pub move_route: MoveRoute, pub walk_anime: bool, pub step_anime: bool, pub direction_fix: bool, pub through: bool, pub always_on_top: bool, - pub trigger: i32, + pub trigger: EventTrigger, pub list: Vec, } +#[derive(serde::Deserialize, serde::Serialize)] +#[derive(alox_48::Deserialize, alox_48::Serialize)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] +#[derive(strum::Display, strum::EnumIter)] +#[serde(try_from = "u8", into = "u8")] +#[marshal(try_from = "u8", into = "u8")] +#[repr(u8)] +pub enum EventTrigger { + #[strum(to_string = "Action Button")] + ActionButton, + #[strum(to_string = "Player Touch")] + PlayerTouch, + #[strum(to_string = "Event Touch")] + EventTouch, + #[strum(to_string = "Autorun")] + Autorun, + #[strum(to_string = "Parallel Process")] + Parallel, +} + +#[derive(serde::Deserialize, serde::Serialize)] +#[derive(alox_48::Deserialize, alox_48::Serialize)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] +#[derive(strum::Display, strum::EnumIter)] +#[serde(try_from = "u8", into = "u8")] +#[marshal(try_from = "u8", into = "u8")] +#[repr(u8)] +pub enum MoveType { + Fixed, + Random, + Approach, + Custom, +} + +#[derive(serde::Deserialize, serde::Serialize)] +#[derive(alox_48::Deserialize, alox_48::Serialize)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] +#[derive(strum::Display, strum::EnumIter)] +#[serde(try_from = "u8", into = "u8")] +#[marshal(try_from = "u8", into = "u8")] +#[repr(u8)] +pub enum MoveFreq { + Lowest = 1, + Lower, + Low, + High, + Higher, + Highest, +} + +#[derive(serde::Deserialize, serde::Serialize)] +#[derive(alox_48::Deserialize, alox_48::Serialize)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(num_enum::TryFromPrimitive, num_enum::IntoPrimitive)] +#[derive(strum::Display, strum::EnumIter)] +#[serde(try_from = "u8", into = "u8")] +#[marshal(try_from = "u8", into = "u8")] +#[repr(u8)] +pub enum MoveSpeed { + Slowest = 1, + Slower, + Slow, + Fast, + Faster, + Fastest, +} + impl Default for EventPage { fn default() -> Self { Self { condition: EventCondition::default(), graphic: Graphic::default(), - move_type: 0, - move_speed: 3, - move_frequency: 3, + move_type: MoveType::Fixed, + move_speed: MoveSpeed::Slow, + move_frequency: MoveFreq::Low, move_route: MoveRoute::default(), walk_anime: true, step_anime: false, direction_fix: false, through: false, always_on_top: false, - trigger: 0, + trigger: EventTrigger::ActionButton, list: vec![], } } @@ -156,7 +226,7 @@ pub struct EventCondition { pub switch2_id: usize, pub variable_id: usize, pub variable_value: i32, - pub self_switch_ch: String, + pub self_switch_ch: SelfSwitch, } impl Default for EventCondition { @@ -170,7 +240,43 @@ impl Default for EventCondition { switch2_id: 0, variable_id: 0, variable_value: 0, - self_switch_ch: "A".to_string(), + self_switch_ch: SelfSwitch::A, + } + } +} + +#[derive(serde::Deserialize, serde::Serialize)] +#[derive(alox_48::Deserialize, alox_48::Serialize)] +#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(strum::Display, strum::EnumIter)] +#[serde(from = "String", into = "String")] +#[marshal(from = "String", into = "String")] +pub enum SelfSwitch { + A, + B, + C, + D, +} + +impl From for SelfSwitch { + fn from(value: String) -> Self { + match value.as_str() { + "A" => Self::A, + "B" => Self::B, + "C" => Self::C, + "D" => Self::D, + _ => panic!("wrong value for self switch"), + } + } +} + +impl From for String { + fn from(val: SelfSwitch) -> Self { + match val { + SelfSwitch::A => "A".to_string(), + SelfSwitch::B => "B".to_string(), + SelfSwitch::C => "C".to_string(), + SelfSwitch::D => "D".to_string(), } } } diff --git a/crates/ui/src/tabs/map/mod.rs b/crates/ui/src/tabs/map/mod.rs index a967058d..11baff76 100644 --- a/crates/ui/src/tabs/map/mod.rs +++ b/crates/ui/src/tabs/map/mod.rs @@ -477,8 +477,9 @@ impl luminol_core::Tab for Tab { { // Double-click/press enter on events to edit them if ui.input(|i| !i.modifiers.command) { + let event = map.events[selected_event_id].clone(); self.event_windows - .add_window(event_edit::Window::new(selected_event_id, self.id)); + .add_window(event_edit::Window::new(event, self.id)); } } diff --git a/crates/ui/src/tabs/map/util.rs b/crates/ui/src/tabs/map/util.rs index 1192a876..847033af 100644 --- a/crates/ui/src/tabs/map/util.rs +++ b/crates/ui/src/tabs/map/util.rs @@ -227,20 +227,15 @@ impl super::Tab { return None; }; - map.events.insert( + let event = luminol_data::rpg::Event::new( + self.view.cursor_pos.x as i32, + self.view.cursor_pos.y as i32, new_event_id, - luminol_data::rpg::Event::new( - self.view.cursor_pos.x as i32, - self.view.cursor_pos.y as i32, - new_event_id, - ), ); + map.events.insert(new_event_id, event.clone()); self.event_windows - .add_window(crate::windows::event_edit::Window::new( - new_event_id, - self.id, - )); + .add_window(crate::windows::event_edit::Window::new(event, self.id)); Some(new_event_id) } diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index b01a4f79..5bbdcb2a 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -22,55 +22,144 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. +use luminol_core::Modal; +use luminol_data::rpg; +use luminol_modals::{switch, variable}; + /// The event editor window. pub struct Window { - id: usize, map_id: usize, - _selected_page: usize, - name: String, - _viewed_tab: u8, + event: rpg::Event, + selected_page: usize, - _switch_modal_1: Option, - _switch_modal_2: Option, - _variable_modal: Option, + switch_1_modal: switch::Modal, + switch_2_modal: switch::Modal, + variable_modal: variable::Modal, } impl Window { /// Create a new event editor. - pub fn new(id: usize, map_id: usize) -> Self { + pub fn new(event: rpg::Event, map_id: usize) -> Self { Self { - id, map_id, - _selected_page: 0, - name: String::from("(unknown)"), - _viewed_tab: 2, + event, + selected_page: 0, - _switch_modal_1: None, - _switch_modal_2: None, - _variable_modal: None, + switch_1_modal: switch::Modal::default(), + switch_2_modal: switch::Modal::default(), + variable_modal: variable::Modal::default(), } } } impl luminol_core::Window for Window { fn name(&self) -> String { - format!("Event: {}, {} in Map {}", self.name, self.id, self.map_id) + format!("Event '{}' ID {}", self.event.name, self.event.id) } fn id(&self) -> egui::Id { egui::Id::new("luminol_event_edit") .with(self.map_id) - .with(self.id) + .with(self.event.id) } // This needs an overhaul fn show( &mut self, - _ctx: &egui::Context, - _open: &mut bool, - _update_state: &mut luminol_core::UpdateState<'_>, + ctx: &egui::Context, + open: &mut bool, + update_state: &mut luminol_core::UpdateState<'_>, ) { - todo!() + egui::Window::new(self.name()).open(open).show(ctx, |ui| { + ui.horizontal(|ui| { + ui.label("Page: "); + for i in 0..self.event.pages.len() { + ui.selectable_value(&mut self.selected_page, i, format!("{}", i + 1)); + } + + if ui + .button(egui::RichText::new("Add").color(egui::Color32::LIGHT_GREEN)) + .clicked() + { + self.event.pages.push(rpg::EventPage::default()); + self.selected_page = self.event.pages.len() - 1; + } + + let button = egui::Button::new( + egui::RichText::new("Delete").color(egui::Color32::LIGHT_RED), + ); + if ui.add_enabled(self.event.pages.len() > 1, button).clicked() { + self.event.pages.remove(self.selected_page); + self.selected_page = self.selected_page.saturating_sub(1); + } + if ui.button(egui::RichText::new("Clear")).clicked() { + self.event.pages[self.selected_page] = rpg::EventPage::default(); + } + }); + ui.separator(); + + let id_source = self.id(); + let page = &mut self.event.pages[self.selected_page]; + + ui.columns(2, |columns| { + columns[0].horizontal(|ui| { + ui.checkbox(&mut page.condition.switch1_valid, "Switch"); + ui.add_enabled( + page.condition.switch1_valid, + self.switch_1_modal + .button(&mut page.condition.switch1_id, update_state), + ); + ui.label("is ON"); + }); + columns[1].horizontal(|ui| { + ui.checkbox(&mut page.condition.switch2_valid, "Switch"); + ui.add_enabled( + page.condition.switch2_valid, + self.switch_2_modal + .button(&mut page.condition.switch2_id, update_state), + ); + ui.label("is ON"); + }); + columns[0].horizontal(|ui| { + ui.checkbox(&mut page.condition.variable_valid, "Variable"); + ui.add_enabled( + page.condition.variable_valid, + self.variable_modal + .button(&mut page.condition.variable_id, update_state), + ); + ui.label("is"); + ui.add_enabled( + page.condition.variable_valid, + egui::DragValue::new(&mut page.condition.variable_value), + ); + ui.label("or above"); + }); + columns[1].horizontal(|ui| { + ui.checkbox(&mut page.condition.self_switch_valid, "Self Switch"); + ui.add_enabled( + page.condition.self_switch_valid, + luminol_components::EnumMenuButton::new( + &mut page.condition.self_switch_ch, + id_source.with("self_switch_ch"), + ), + ); + ui.label("is ON"); + }); + }); + ui.separator(); + ui.columns(2, |columns| { + columns[0].checkbox(&mut page.walk_anime, "Move Animation"); + columns[0].checkbox(&mut page.step_anime, "Stop Animation"); + columns[0].checkbox(&mut page.direction_fix, "Direction Fix"); + columns[0].checkbox(&mut page.through, "Through"); + columns[0].checkbox(&mut page.always_on_top, "Always On Top"); + + columns[1].add(luminol_components::EnumMenuButton::new( + &mut page.trigger, + id_source.with("trigger"), + )); + }); + }); } fn requires_filesystem(&self) -> bool { From 66cf00f96fc171be7036fe7880b31fc91dab6931 Mon Sep 17 00:00:00 2001 From: Speak2Erase Date: Tue, 25 Jun 2024 08:43:05 -0700 Subject: [PATCH 04/67] Fix modal id conflict --- crates/modals/src/switch.rs | 30 ++++++++++++----- crates/modals/src/variable.rs | 33 +++++++++++++------ crates/ui/src/windows/common_event_edit.rs | 38 +--------------------- crates/ui/src/windows/event_edit.rs | 7 ++-- 4 files changed, 49 insertions(+), 59 deletions(-) diff --git a/crates/modals/src/switch.rs b/crates/modals/src/switch.rs index be4e2ae0..2cdf8d2c 100644 --- a/crates/modals/src/switch.rs +++ b/crates/modals/src/switch.rs @@ -24,10 +24,12 @@ use luminol_components::UiExt; -/// The switch picker modal. -#[derive(Default)] -pub enum Modal { - #[default] +pub struct Modal { + state: State, + id: egui::Id, +} + +enum State { Closed, Open { search_text: String, @@ -35,6 +37,15 @@ pub enum Modal { }, } +impl Modal { + pub fn new(id: egui::Id) -> Self { + Self { + state: State::Closed, + id, + } + } +} + impl luminol_core::Modal for Modal { type Data = usize; @@ -54,7 +65,7 @@ impl luminol_core::Modal for Modal { let button_response = ui.button(button_text); if button_response.clicked() { - *self = Self::Open { + self.state = State::Open { search_text: String::new(), switch_id: *data, }; @@ -68,7 +79,7 @@ impl luminol_core::Modal for Modal { } fn reset(&mut self) { - *self = Self::Closed; + self.state = State::Closed; } } @@ -83,10 +94,10 @@ impl Modal { let mut keep_open = true; let mut needs_save = false; - let Self::Open { + let State::Open { search_text, switch_id, - } = self + } = &mut self.state else { return; }; @@ -94,6 +105,7 @@ impl Modal { egui::Window::new("Switch Picker") .resizable(false) .open(&mut win_open) + .id(self.id) .show(ctx, |ui| { let system = update_state.data.system(); @@ -133,7 +145,7 @@ impl Modal { } if !win_open || !keep_open { - *self = Self::Closed; + self.state = State::Closed; } } } diff --git a/crates/modals/src/variable.rs b/crates/modals/src/variable.rs index 3b6b84db..96cd019a 100644 --- a/crates/modals/src/variable.rs +++ b/crates/modals/src/variable.rs @@ -24,10 +24,13 @@ use luminol_components::UiExt; -#[derive(Default)] -/// The variable picker modal. -pub enum Modal { - #[default] +// TODO generalize modal into id modal +pub struct Modal { + state: State, + id: egui::Id, +} + +enum State { Closed, Open { search_text: String, @@ -35,6 +38,15 @@ pub enum Modal { }, } +impl Modal { + pub fn new(id: egui::Id) -> Self { + Self { + state: State::Closed, + id, + } + } +} + impl luminol_core::Modal for Modal { type Data = usize; @@ -54,8 +66,8 @@ impl luminol_core::Modal for Modal { let button_response = ui.button(button_text); if button_response.clicked() { - *self = Self::Open { - search_text: String::new(), + self.state = State::Open { + search_text: "".to_string(), variable_id: *data, }; } @@ -68,7 +80,7 @@ impl luminol_core::Modal for Modal { } fn reset(&mut self) { - *self = Self::Closed; + self.state = State::Closed; } } @@ -83,10 +95,10 @@ impl Modal { let mut keep_open = true; let mut needs_save = false; - let Self::Open { + let State::Open { search_text, variable_id, - } = self + } = &mut self.state else { return; }; @@ -94,6 +106,7 @@ impl Modal { egui::Window::new("Variable Picker") .resizable(false) .open(&mut win_open) + .id(self.id) .show(ctx, |ui| { let system = update_state.data.system(); @@ -135,7 +148,7 @@ impl Modal { } if !win_open || !keep_open { - *self = Self::Closed; + self.state = State::Closed; } } } diff --git a/crates/ui/src/windows/common_event_edit.rs b/crates/ui/src/windows/common_event_edit.rs index 10c3de22..cb536c7c 100644 --- a/crates/ui/src/windows/common_event_edit.rs +++ b/crates/ui/src/windows/common_event_edit.rs @@ -63,43 +63,7 @@ impl luminol_core::Window for Window { .id(egui::Id::new("common_events_edit")) .open(open) .show(ctx, |ui| { - egui::SidePanel::left("common_events_side_panel").show_inside(ui, |ui| { - let common_events = update_state.data.common_events(); - - egui::ScrollArea::both().auto_shrink([false; 2]).show_rows( - ui, - ui.text_style_height(&egui::TextStyle::Body), - common_events.data.len(), - |ui, rows| { - for (ele, event) in common_events - .data - .iter() - .enumerate() - .filter(|(ele, _)| rows.contains(ele)) - { - if ui - .selectable_value( - &mut self.selected_id, - ele, - format!("{}: {}", event.id, event.name), - ) - .double_clicked() - { - self.tabs.add_tab(CommonEventTab { - event: event.clone(), - force_close: false, - switch_modal: Default::default(), - command_view: luminol_components::CommandView::new( - format!("common_event_{ele}"), - ), - }); - } - } - }, - ); - }); - - self.tabs.ui(ui, update_state); + // TODO }); } diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index 5bbdcb2a..4d358f27 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -40,14 +40,15 @@ pub struct Window { impl Window { /// Create a new event editor. pub fn new(event: rpg::Event, map_id: usize) -> Self { + let id_source = egui::Id::new("event_edit").with(event.id).with(map_id); Self { map_id, event, selected_page: 0, - switch_1_modal: switch::Modal::default(), - switch_2_modal: switch::Modal::default(), - variable_modal: variable::Modal::default(), + switch_1_modal: switch::Modal::new(id_source.with("switch_1_modal")), + switch_2_modal: switch::Modal::new(id_source.with("switch_2_modal")), + variable_modal: variable::Modal::new(id_source.with("variable_modal")), } } } From 436ea1a8fb0991e3ea6995cd0a7b391975170dcb Mon Sep 17 00:00:00 2001 From: Speak2Erase Date: Tue, 25 Jun 2024 10:40:20 -0700 Subject: [PATCH 05/67] Use unified modal --- Cargo.lock | 51 +++++ crates/data/src/shared/event.rs | 2 + crates/modals/Cargo.toml | 2 + crates/modals/src/database_modal/mod.rs | 218 +++++++++++++++++++ crates/modals/src/database_modal/switch.rs | 59 +++++ crates/modals/src/database_modal/variable.rs | 59 +++++ crates/modals/src/lib.rs | 2 + crates/modals/src/switch.rs | 128 +---------- crates/modals/src/variable.rs | 132 +---------- 9 files changed, 396 insertions(+), 257 deletions(-) create mode 100644 crates/modals/src/database_modal/mod.rs create mode 100644 crates/modals/src/database_modal/switch.rs create mode 100644 crates/modals/src/database_modal/variable.rs 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; From 4a5a2e875f7a7ea583f36eb3758a37b28bab4bd7 Mon Sep 17 00:00:00 2001 From: Speak2Erase Date: Tue, 25 Jun 2024 10:42:19 -0700 Subject: [PATCH 06/67] Replace import hack --- crates/modals/src/database_modal/mod.rs | 3 +++ crates/modals/src/lib.rs | 5 ----- crates/modals/src/switch.rs | 25 --------------------- crates/modals/src/variable.rs | 26 ---------------------- crates/ui/src/windows/common_event_edit.rs | 3 ++- crates/ui/src/windows/event_edit.rs | 14 ++++++------ 6 files changed, 12 insertions(+), 64 deletions(-) delete mode 100644 crates/modals/src/switch.rs delete mode 100644 crates/modals/src/variable.rs diff --git a/crates/modals/src/database_modal/mod.rs b/crates/modals/src/database_modal/mod.rs index 4435e14c..c1f7e89c 100644 --- a/crates/modals/src/database_modal/mod.rs +++ b/crates/modals/src/database_modal/mod.rs @@ -37,6 +37,9 @@ pub struct Modal { _phantom: PhantomData, // so that M is constrained } +pub type SwitchModal = Modal; +pub type VariableModal = Modal; + enum State { Closed, Open { diff --git a/crates/modals/src/lib.rs b/crates/modals/src/lib.rs index 75d0d625..f6a702a5 100644 --- a/crates/modals/src/lib.rs +++ b/crates/modals/src/lib.rs @@ -22,11 +22,6 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. -/// The switch picker. -pub mod switch; -/// The variable picker. -pub mod variable; - pub mod sound_picker; pub mod graphic_picker; diff --git a/crates/modals/src/switch.rs b/crates/modals/src/switch.rs deleted file mode 100644 index 1ef7ad1c..00000000 --- a/crates/modals/src/switch.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (C) 2023 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 type Modal = crate::database_modal::Modal; diff --git a/crates/modals/src/variable.rs b/crates/modals/src/variable.rs deleted file mode 100644 index 2eb87471..00000000 --- a/crates/modals/src/variable.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2023 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. - -// kinda a hack so we dont need to rewrite code -pub type Modal = crate::database_modal::Modal; diff --git a/crates/ui/src/windows/common_event_edit.rs b/crates/ui/src/windows/common_event_edit.rs index cb536c7c..538e1eea 100644 --- a/crates/ui/src/windows/common_event_edit.rs +++ b/crates/ui/src/windows/common_event_edit.rs @@ -23,6 +23,7 @@ // Program grant you additional permission to convey the resulting work. use luminol_core::Modal; +use luminol_modals::database_modal; /// The common event editor. pub struct Window { @@ -75,7 +76,7 @@ impl luminol_core::Window for Window { struct CommonEventTab { event: luminol_data::rpg::CommonEvent, force_close: bool, - switch_modal: luminol_modals::switch::Modal, + switch_modal: database_modal::SwitchModal, command_view: luminol_components::CommandView, } diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index 4d358f27..056c2058 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -24,7 +24,7 @@ use luminol_core::Modal; use luminol_data::rpg; -use luminol_modals::{switch, variable}; +use luminol_modals::database_modal; /// The event editor window. pub struct Window { @@ -32,9 +32,9 @@ pub struct Window { event: rpg::Event, selected_page: usize, - switch_1_modal: switch::Modal, - switch_2_modal: switch::Modal, - variable_modal: variable::Modal, + switch_1_modal: database_modal::SwitchModal, + switch_2_modal: database_modal::SwitchModal, + variable_modal: database_modal::VariableModal, } impl Window { @@ -46,9 +46,9 @@ impl Window { event, selected_page: 0, - switch_1_modal: switch::Modal::new(id_source.with("switch_1_modal")), - switch_2_modal: switch::Modal::new(id_source.with("switch_2_modal")), - variable_modal: variable::Modal::new(id_source.with("variable_modal")), + switch_1_modal: database_modal::Modal::new(id_source.with("switch_1_modal")), + switch_2_modal: database_modal::Modal::new(id_source.with("switch_2_modal")), + variable_modal: database_modal::Modal::new(id_source.with("variable_modal")), } } } From 6067f2d403958ea5a2421aac6c627f71595ff47f Mon Sep 17 00:00:00 2001 From: Speak2Erase Date: Tue, 25 Jun 2024 11:28:00 -0700 Subject: [PATCH 07/67] Add most of the event ui --- crates/components/src/lib.rs | 30 ++++ crates/ui/src/windows/event_edit.rs | 222 ++++++++++++++++++---------- 2 files changed, 171 insertions(+), 81 deletions(-) diff --git a/crates/components/src/lib.rs b/crates/components/src/lib.rs index 78c3abd4..d9890e4b 100644 --- a/crates/components/src/lib.rs +++ b/crates/components/src/lib.rs @@ -84,6 +84,36 @@ impl<'e, T: ToString + PartialEq + strum::IntoEnumIterator> egui::Widget for Enu } } +pub struct EnumRadioList<'e, T> { + current_value: &'e mut T, +} + +impl<'e, T> EnumRadioList<'e, T> { + pub fn new(current_value: &'e mut T) -> Self { + Self { current_value } + } +} + +impl<'e, T: ToString + PartialEq + strum::IntoEnumIterator> egui::Widget for EnumRadioList<'e, T> { + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + let mut changed = false; + let mut response = ui + .vertical(|ui| { + for variant in T::iter() { + let text = variant.to_string(); + if ui.radio_value(self.current_value, variant, text).changed() { + changed = true; + } + } + }) + .response; + if changed { + response.mark_changed(); + } + response + } +} + pub struct Field { name: String, widget: T, diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index 056c2058..cb556e82 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -22,6 +22,7 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. +use egui::Widget; use luminol_core::Modal; use luminol_data::rpg; use luminol_modals::database_modal; @@ -40,7 +41,9 @@ pub struct Window { impl Window { /// Create a new event editor. pub fn new(event: rpg::Event, map_id: usize) -> Self { - let id_source = egui::Id::new("event_edit").with(event.id).with(map_id); + let id_source = egui::Id::new("luminol_event_edit") + .with(event.id) + .with(map_id); Self { map_id, event, @@ -72,94 +75,151 @@ impl luminol_core::Window for Window { update_state: &mut luminol_core::UpdateState<'_>, ) { egui::Window::new(self.name()).open(open).show(ctx, |ui| { - ui.horizontal(|ui| { - ui.label("Page: "); - for i in 0..self.event.pages.len() { - ui.selectable_value(&mut self.selected_page, i, format!("{}", i + 1)); - } - - if ui - .button(egui::RichText::new("Add").color(egui::Color32::LIGHT_GREEN)) - .clicked() - { - self.event.pages.push(rpg::EventPage::default()); - self.selected_page = self.event.pages.len() - 1; - } - - let button = egui::Button::new( - egui::RichText::new("Delete").color(egui::Color32::LIGHT_RED), - ); - if ui.add_enabled(self.event.pages.len() > 1, button).clicked() { - self.event.pages.remove(self.selected_page); - self.selected_page = self.selected_page.saturating_sub(1); - } - if ui.button(egui::RichText::new("Clear")).clicked() { - self.event.pages[self.selected_page] = rpg::EventPage::default(); - } - }); - ui.separator(); - let id_source = self.id(); - let page = &mut self.event.pages[self.selected_page]; + egui::TopBottomPanel::top(id_source.with("top_panel")).show_inside(ui, |ui| { + ui.horizontal(|ui| { + ui.label("Page: "); + for i in 0..self.event.pages.len() { + ui.selectable_value(&mut self.selected_page, i, format!("{}", i + 1)); + } - ui.columns(2, |columns| { - columns[0].horizontal(|ui| { - ui.checkbox(&mut page.condition.switch1_valid, "Switch"); - ui.add_enabled( - page.condition.switch1_valid, - self.switch_1_modal - .button(&mut page.condition.switch1_id, update_state), + if ui + .button(egui::RichText::new("Add").color(egui::Color32::LIGHT_GREEN)) + .clicked() + { + self.event.pages.push(rpg::EventPage::default()); + self.selected_page = self.event.pages.len() - 1; + } + + let button = egui::Button::new( + egui::RichText::new("Delete").color(egui::Color32::LIGHT_RED), ); - ui.label("is ON"); + if ui.add_enabled(self.event.pages.len() > 1, button).clicked() { + self.event.pages.remove(self.selected_page); + self.selected_page = self.selected_page.saturating_sub(1); + } + if ui.button(egui::RichText::new("Clear")).clicked() { + self.event.pages[self.selected_page] = rpg::EventPage::default(); + } }); - columns[1].horizontal(|ui| { - ui.checkbox(&mut page.condition.switch2_valid, "Switch"); - ui.add_enabled( - page.condition.switch2_valid, - self.switch_2_modal - .button(&mut page.condition.switch2_id, update_state), - ); - ui.label("is ON"); + }); + + let page = &mut self.event.pages[self.selected_page]; + + egui::SidePanel::left(id_source.with("side_panel")).show_inside(ui, |ui| { + ui.label("Conditions"); + ui.group(|ui| { + ui.horizontal(|ui| { + ui.checkbox(&mut page.condition.switch1_valid, "Switch"); + ui.add_enabled( + page.condition.switch1_valid, + self.switch_1_modal + .button(&mut page.condition.switch1_id, update_state), + ); + ui.label("is ON"); + }); + ui.horizontal(|ui| { + ui.checkbox(&mut page.condition.switch2_valid, "Switch"); + ui.add_enabled( + page.condition.switch2_valid, + self.switch_2_modal + .button(&mut page.condition.switch2_id, update_state), + ); + ui.label("is ON"); + }); + ui.horizontal(|ui| { + ui.checkbox(&mut page.condition.variable_valid, "Variable"); + ui.add_enabled( + page.condition.variable_valid, + self.variable_modal + .button(&mut page.condition.variable_id, update_state), + ); + ui.label("is"); + ui.add_enabled( + page.condition.variable_valid, + egui::DragValue::new(&mut page.condition.variable_value), + ); + ui.label("or above"); + }); + ui.horizontal(|ui| { + ui.checkbox(&mut page.condition.self_switch_valid, "Self Switch"); + // TODO add self switch text box (config option) + ui.add_enabled( + // FIXME ensure shrink + page.condition.self_switch_valid, + luminol_components::EnumMenuButton::new( + &mut page.condition.self_switch_ch, + id_source.with("self_switch_ch"), + ), + ); + ui.label("is ON"); + // ensure we expand to fit the side panel + ui.add_space(ui.available_width()); + }); }); - columns[0].horizontal(|ui| { - ui.checkbox(&mut page.condition.variable_valid, "Variable"); - ui.add_enabled( - page.condition.variable_valid, - self.variable_modal - .button(&mut page.condition.variable_id, update_state), - ); - ui.label("is"); - ui.add_enabled( - page.condition.variable_valid, - egui::DragValue::new(&mut page.condition.variable_value), - ); - ui.label("or above"); + + ui.horizontal(|ui| { + ui.vertical(|ui| { + ui.label("Graphic"); + // TODO + }); + ui.vertical(|ui| { + ui.label("Autonomous Movement"); + ui.group(|ui| { + // FIXME these expand to fit, which is kinda annoying + ui.horizontal(|ui| { + ui.label("Move Type"); + luminol_components::EnumComboBox::new( + id_source.with("move_type"), + &mut page.move_type, + ) + .ui(ui); + }); + ui.add_enabled( + page.move_type == luminol_data::rpg::MoveType::Custom, + egui::Button::new("Move Route..."), + ); // TODO + ui.horizontal(|ui| { + ui.label("Move Speed"); + luminol_components::EnumComboBox::new( + id_source.with("move_speed"), + &mut page.move_speed, + ) + .ui(ui); + }); + ui.horizontal(|ui| { + ui.label("Move Frequency"); + luminol_components::EnumComboBox::new( + id_source.with("move_frequency"), + &mut page.move_frequency, + ) + .ui(ui); + }); + }); + }); }); - columns[1].horizontal(|ui| { - ui.checkbox(&mut page.condition.self_switch_valid, "Self Switch"); - ui.add_enabled( - page.condition.self_switch_valid, - luminol_components::EnumMenuButton::new( - &mut page.condition.self_switch_ch, - id_source.with("self_switch_ch"), - ), - ); - ui.label("is ON"); + + ui.columns(2, |columns| { + let [left, right] = columns else { + unreachable!() + }; + + left.label("Options"); + left.group(|ui| { + ui.style_mut().wrap = Some(false); + ui.checkbox(&mut page.walk_anime, "Move Animation"); + ui.checkbox(&mut page.step_anime, "Step Animation"); + ui.checkbox(&mut page.direction_fix, "Direction Fix"); + ui.checkbox(&mut page.through, "Through"); + ui.checkbox(&mut page.always_on_top, "Always on Top"); + }); + + right.label("Trigger"); + right.group(|ui| { + luminol_components::EnumRadioList::new(&mut page.trigger).ui(ui); + }); }); }); - ui.separator(); - ui.columns(2, |columns| { - columns[0].checkbox(&mut page.walk_anime, "Move Animation"); - columns[0].checkbox(&mut page.step_anime, "Stop Animation"); - columns[0].checkbox(&mut page.direction_fix, "Direction Fix"); - columns[0].checkbox(&mut page.through, "Through"); - columns[0].checkbox(&mut page.always_on_top, "Always On Top"); - - columns[1].add(luminol_components::EnumMenuButton::new( - &mut page.trigger, - id_source.with("trigger"), - )); - }); }); } From cbfba0e8b00e2211f4df9aaadce7fccea57d82a6 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 25 Jun 2024 12:23:29 -0700 Subject: [PATCH 08/67] start implementing the graphic picker --- crates/components/src/map_view.rs | 3 +- crates/components/src/tilepicker.rs | 1 + crates/core/src/lib.rs | 8 ++ crates/graphics/src/event.rs | 22 ++-- crates/graphics/src/tilepicker.rs | 13 +- crates/modals/src/event_graphic_picker.rs | 139 ++++++++++++++++++++++ crates/modals/src/lib.rs | 2 + crates/ui/src/tabs/map/mod.rs | 10 +- crates/ui/src/tabs/map/util.rs | 14 ++- crates/ui/src/windows/event_edit.rs | 33 ++++- 10 files changed, 216 insertions(+), 29 deletions(-) create mode 100644 crates/modals/src/event_graphic_picker.rs diff --git a/crates/components/src/map_view.rs b/crates/components/src/map_view.rs index 2b6596e2..299f28b7 100644 --- a/crates/components/src/map_view.rs +++ b/crates/components/src/map_view.rs @@ -456,11 +456,12 @@ impl MapView { &update_state.graphics, glam::vec2(event_size.x, event_size.y), ); + let graphic = &event.pages[0].graphic; // FIXME handle missing first page (should never happen though...) let sprite = luminol_graphics::Event::new_standalone( &update_state.graphics, update_state.filesystem, &viewport, - event, + graphic, &self.map.atlas, ) .unwrap() diff --git a/crates/components/src/tilepicker.rs b/crates/components/src/tilepicker.rs index 81775c43..697fa765 100644 --- a/crates/components/src/tilepicker.rs +++ b/crates/components/src/tilepicker.rs @@ -77,6 +77,7 @@ impl Tilepicker { &update_state.graphics, tileset, update_state.filesystem, + false, )?; let mut brush_seed = [0u8; 16]; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 4d5aa81f..7a0c4a37 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -48,6 +48,14 @@ pub use project_manager::ProjectManager; pub use alox_48; pub use data_cache::format_traced_error; +pub mod prelude { + pub use crate::{Modal, Tab, UpdateState, Window}; + pub use luminol_audio::Source; + pub use luminol_data::rpg; + pub use luminol_filesystem::FileSystem; + pub use luminol_graphics::*; +} + static GIT_REVISION: once_cell::sync::OnceCell<&'static str> = once_cell::sync::OnceCell::new(); pub fn set_git_revision(revision: &'static str) { diff --git a/crates/graphics/src/event.rs b/crates/graphics/src/event.rs index 7da37819..83824d81 100644 --- a/crates/graphics/src/event.rs +++ b/crates/graphics/src/event.rs @@ -116,15 +116,11 @@ impl Event { graphics_state: &GraphicsState, filesystem: &impl luminol_filesystem::FileSystem, viewport: &Viewport, - event: &luminol_data::rpg::Event, + graphic: &luminol_data::rpg::Graphic, atlas: &Atlas, ) -> color_eyre::Result> { - let Some(page) = event.pages.first() else { - color_eyre::eyre::bail!("event does not have first page"); - }; - let mut is_placeholder = false; - let texture = if let Some(ref filename) = page.graphic.character_name { + let texture = if let Some(ref filename) = graphic.character_name { let texture = graphics_state .texture_loader .load_now_dir(filesystem, "Graphics/Characters", filename) @@ -137,13 +133,13 @@ impl Event { graphics_state.texture_loader.placeholder_texture() } } - } else if page.graphic.tile_id.is_some() { + } else if graphic.tile_id.is_some() { atlas.atlas_texture.clone() } else { return Ok(None); }; - let (quad, sprite_size) = if let Some(id) = page.graphic.tile_id { + let (quad, sprite_size) = if let Some(id) = graphic.tile_id { // Why does this have to be + 1? let quad = atlas.calc_quad((id + 1) as i16); @@ -167,8 +163,8 @@ impl Event { // Reduced by 0.01 px on all sides to reduce texture bleeding let tex_coords = egui::Rect::from_min_size( egui::pos2( - page.graphic.pattern as f32 * cw + 0.01, - (page.graphic.direction as f32 - 2.) / 2. * ch + 0.01, + graphic.pattern as f32 * cw + 0.01, + (graphic.direction as f32 - 2.) / 2. * ch + 0.01, ), egui::vec2(cw - 0.02, ch - 0.02), ); @@ -182,9 +178,9 @@ impl Event { let sprite = Sprite::new( graphics_state, quad, - page.graphic.character_hue, - page.graphic.opacity, - page.graphic.blend_type, + graphic.character_hue, + graphic.opacity, + graphic.blend_type, &texture, viewport, transform, diff --git a/crates/graphics/src/tilepicker.rs b/crates/graphics/src/tilepicker.rs index fa0e3ba3..d47eb2c1 100644 --- a/crates/graphics/src/tilepicker.rs +++ b/crates/graphics/src/tilepicker.rs @@ -48,15 +48,20 @@ impl Tilepicker { graphics_state: &GraphicsState, tileset: &luminol_data::rpg::Tileset, filesystem: &impl luminol_filesystem::FileSystem, + exclude_autotiles: bool, ) -> color_eyre::Result { let atlas = graphics_state .atlas_loader .load_atlas(graphics_state, filesystem, tileset)?; - let tilepicker_data = (47..(384 + 47)) - .step_by(48) - .chain(384..(atlas.tileset_height as i16 / 32 * 8 + 384)) - .collect_vec(); + let tilepicker_data = if exclude_autotiles { + (384..(atlas.tileset_height as i16 / 32 * 8 + 384)).collect_vec() + } else { + (47..(384 + 47)) + .step_by(48) + .chain(384..(atlas.tileset_height as i16 / 32 * 8 + 384)) + .collect_vec() + }; let tilepicker_data = luminol_data::Table3::new_data( 8, 1 + (atlas.tileset_height / 32) as usize, diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs new file mode 100644 index 00000000..bb6e7738 --- /dev/null +++ b/crates/modals/src/event_graphic_picker.rs @@ -0,0 +1,139 @@ +// 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 luminol_core::prelude::*; + +pub struct Modal { + entries: Vec, + open: bool, + id_source: egui::Id, + + tilepicker: Tilepicker, + + button_viewport: Viewport, + button_sprite: Option, + + sprite: Option<(Viewport, Event)>, +} + +impl Modal { + pub fn new( + update_state: &UpdateState<'_>, + graphic: &rpg::Graphic, + tileset: &rpg::Tileset, + id_source: egui::Id, + ) -> Self { + // TODO error handling + let entries = update_state + .filesystem + .read_dir("Graphics/Characters") + .unwrap() + .into_iter() + .map(|m| { + m.path + .strip_prefix("Graphics/Characters") + .unwrap_or(&m.path) + .with_extension("") + }) + .collect(); + + let tilepicker = Tilepicker::new( + &update_state.graphics, + tileset, + update_state.filesystem, + false, + ) + .unwrap(); + + let button_viewport = Viewport::new(&update_state.graphics, Default::default()); + let button_sprite = Event::new_standalone( + &update_state.graphics, + update_state.filesystem, + &button_viewport, + graphic, + &tilepicker.atlas, + ) + .unwrap(); + + Self { + entries, + open: false, + id_source, + + tilepicker, + + button_viewport, + button_sprite, + + sprite: None, + } + } +} + +impl luminol_core::Modal for Modal { + type Data = luminol_data::rpg::Graphic; + + fn button<'m>( + &'m mut self, + data: &'m mut Self::Data, + update_state: &'m mut UpdateState<'_>, + ) -> impl egui::Widget + 'm { + move |ui: &mut egui::Ui| { + let button_text = match data { + rpg::Graphic { + character_name: Some(name), + .. + } => name.to_string(), + rpg::Graphic { + tile_id: Some(id), .. + } => format!("Tile {id}"), + _ => "None".to_string(), + }; + + let button_response = ui.button(button_text); + if button_response.clicked() { + self.open = true; + } + self.show_window(update_state, ui.ctx(), data); + + button_response + } + } + + fn reset(&mut self) { + self.open = false; + } +} + +impl Modal { + pub fn update_graphic(&mut self, update_state: &UpdateState<'_>, graphic: &rpg::Graphic) {} + + fn show_window( + &mut self, + update_state: &luminol_core::UpdateState<'_>, + ctx: &egui::Context, + data: &mut rpg::Graphic, + ) { + } +} diff --git a/crates/modals/src/lib.rs b/crates/modals/src/lib.rs index f6a702a5..6b118cd1 100644 --- a/crates/modals/src/lib.rs +++ b/crates/modals/src/lib.rs @@ -27,3 +27,5 @@ pub mod sound_picker; pub mod graphic_picker; pub mod database_modal; + +pub mod event_graphic_picker; diff --git a/crates/ui/src/tabs/map/mod.rs b/crates/ui/src/tabs/map/mod.rs index 11baff76..24fe5d32 100644 --- a/crates/ui/src/tabs/map/mod.rs +++ b/crates/ui/src/tabs/map/mod.rs @@ -478,8 +478,12 @@ impl luminol_core::Tab for Tab { // Double-click/press enter on events to edit them if ui.input(|i| !i.modifiers.command) { let event = map.events[selected_event_id].clone(); - self.event_windows - .add_window(event_edit::Window::new(event, self.id)); + self.event_windows.add_window(event_edit::Window::new( + update_state, + event, + self.id, + tileset, + )); } } @@ -551,7 +555,7 @@ impl luminol_core::Tab for Tab { if response.double_clicked() || (is_focused && ui.input(|i| i.key_pressed(egui::Key::Enter))) { - if let Some(id) = self.add_event(&mut map) { + if let Some(id) = self.add_event(update_state, &mut map, tileset) { self.push_to_history( update_state, &mut map, diff --git a/crates/ui/src/tabs/map/util.rs b/crates/ui/src/tabs/map/util.rs index 847033af..4c674780 100644 --- a/crates/ui/src/tabs/map/util.rs +++ b/crates/ui/src/tabs/map/util.rs @@ -199,7 +199,12 @@ impl super::Tab { } } - pub(super) fn add_event(&mut self, map: &mut luminol_data::rpg::Map) -> Option { + pub(super) fn add_event( + &mut self, + update_state: &luminol_core::UpdateState<'_>, + map: &mut luminol_data::rpg::Map, + tileset: &luminol_data::rpg::Tileset, + ) -> Option { let mut first_vacant_id = 1; let mut max_event_id = 0; @@ -235,7 +240,12 @@ impl super::Tab { map.events.insert(new_event_id, event.clone()); self.event_windows - .add_window(crate::windows::event_edit::Window::new(event, self.id)); + .add_window(crate::windows::event_edit::Window::new( + update_state, + event, + map.tileset_id, + tileset, + )); Some(new_event_id) } diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index cb556e82..5be281a7 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -23,9 +23,8 @@ // Program grant you additional permission to convey the resulting work. use egui::Widget; -use luminol_core::Modal; -use luminol_data::rpg; -use luminol_modals::database_modal; +use luminol_core::prelude::*; +use luminol_modals::{database_modal, event_graphic_picker}; /// The event editor window. pub struct Window { @@ -36,14 +35,26 @@ pub struct Window { switch_1_modal: database_modal::SwitchModal, switch_2_modal: database_modal::SwitchModal, variable_modal: database_modal::VariableModal, + graphic_modal: event_graphic_picker::Modal, } impl Window { /// Create a new event editor. - pub fn new(event: rpg::Event, map_id: usize) -> Self { + pub fn new( + update_state: &UpdateState<'_>, + event: rpg::Event, + map_id: usize, + tileset: &rpg::Tileset, + ) -> Self { let id_source = egui::Id::new("luminol_event_edit") .with(event.id) .with(map_id); + let graphic_modal = event_graphic_picker::Modal::new( + update_state, + &event.pages[0].graphic, + tileset, + id_source.with("graphic_modal"), + ); Self { map_id, event, @@ -52,6 +63,7 @@ impl Window { switch_1_modal: database_modal::Modal::new(id_source.with("switch_1_modal")), switch_2_modal: database_modal::Modal::new(id_source.with("switch_2_modal")), variable_modal: database_modal::Modal::new(id_source.with("variable_modal")), + graphic_modal, } } } @@ -67,7 +79,6 @@ impl luminol_core::Window for Window { .with(self.event.id) } - // This needs an overhaul fn show( &mut self, ctx: &egui::Context, @@ -76,6 +87,7 @@ impl luminol_core::Window for Window { ) { egui::Window::new(self.name()).open(open).show(ctx, |ui| { let id_source = self.id(); + let previous_page = self.selected_page; egui::TopBottomPanel::top(id_source.with("top_panel")).show_inside(ui, |ui| { ui.horizontal(|ui| { ui.label("Page: "); @@ -105,6 +117,11 @@ impl luminol_core::Window for Window { }); let page = &mut self.event.pages[self.selected_page]; + if self.selected_page != previous_page { + // we need to update the modal to prevent desyncs + self.graphic_modal + .update_graphic(update_state, &page.graphic); + } egui::SidePanel::left(id_source.with("side_panel")).show_inside(ui, |ui| { ui.label("Conditions"); @@ -161,7 +178,11 @@ impl luminol_core::Window for Window { ui.horizontal(|ui| { ui.vertical(|ui| { ui.label("Graphic"); - // TODO + ui.group(|ui| { + self.graphic_modal + .button(&mut page.graphic, update_state) + .ui(ui); + }); }); ui.vertical(|ui| { ui.label("Autonomous Movement"); From 0bb7840b30cd8cf0909750bb61886cf57b893ffb Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 25 Jun 2024 13:05:01 -0700 Subject: [PATCH 09/67] Add picker window --- Cargo.lock | 2 + crates/modals/Cargo.toml | 2 + crates/modals/src/event_graphic_picker.rs | 47 +++++++++++++++-------- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9759b3eb..ea9b8437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3213,9 +3213,11 @@ dependencies = [ "camino", "egui", "fuzzy-matcher", + "glam", "luminol-components", "luminol-core", "luminol-data", + "luminol-egui-wgpu", "ouroboros", ] diff --git a/crates/modals/Cargo.toml b/crates/modals/Cargo.toml index a575e5a3..31fb90cf 100644 --- a/crates/modals/Cargo.toml +++ b/crates/modals/Cargo.toml @@ -24,6 +24,8 @@ camino.workspace = true luminol-core.workspace = true luminol-data.workspace = true luminol-components.workspace = true +luminol-egui-wgpu.workspace = true +glam.workspace = true fuzzy-matcher = "0.3.7" diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index bb6e7738..addeadec 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -100,24 +100,31 @@ impl luminol_core::Modal for Modal { update_state: &'m mut UpdateState<'_>, ) -> impl egui::Widget + 'm { move |ui: &mut egui::Ui| { - let button_text = match data { - rpg::Graphic { - character_name: Some(name), - .. - } => name.to_string(), - rpg::Graphic { - tile_id: Some(id), .. - } => format!("Tile {id}"), - _ => "None".to_string(), - }; - - let button_response = ui.button(button_text); - if button_response.clicked() { + let desired_size = self + .button_sprite + .as_ref() + .map(|s| s.sprite_size) + .unwrap_or(egui::vec2(32., 32.)); + let (response, painter) = ui.allocate_painter(desired_size, egui::Sense::click()); + + if let Some(sprite) = &mut self.button_sprite { + self.button_viewport.set_size( + &update_state.graphics.render_state, + glam::vec2(desired_size.x, desired_size.y), + ); + let callback = luminol_egui_wgpu::Callback::new_paint_callback( + response.rect, + Painter::new(sprite.prepare(&update_state.graphics)), + ); + painter.add(callback); + } + + if response.clicked() { self.open = true; } self.show_window(update_state, ui.ctx(), data); - button_response + response } } @@ -127,7 +134,17 @@ impl luminol_core::Modal for Modal { } impl Modal { - pub fn update_graphic(&mut self, update_state: &UpdateState<'_>, graphic: &rpg::Graphic) {} + pub fn update_graphic(&mut self, update_state: &UpdateState<'_>, graphic: &rpg::Graphic) { + self.button_sprite = Event::new_standalone( + &update_state.graphics, + update_state.filesystem, + &self.button_viewport, + graphic, + &self.tilepicker.atlas, + ) + .unwrap(); + self.sprite = None; + } fn show_window( &mut self, From eca691e1079532822186804a069cfa7908816154 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 19:12:56 -0700 Subject: [PATCH 10/67] Display graphic picker options --- crates/modals/src/event_graphic_picker.rs | 75 ++++++++++++++++++++--- crates/ui/src/windows/event_edit.rs | 10 +-- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index addeadec..e52380d0 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -28,13 +28,21 @@ pub struct Modal { entries: Vec, open: bool, id_source: egui::Id, + selected: Selected, tilepicker: Tilepicker, button_viewport: Viewport, button_sprite: Option, - sprite: Option<(Viewport, Event)>, + sprite: Option<(Viewport, Sprite)>, +} + +#[derive(PartialEq)] +enum Selected { + None, + Tile(usize), + Graphic(camino::Utf8PathBuf), } impl Modal { @@ -76,10 +84,19 @@ impl Modal { ) .unwrap(); + let selected = if let Some(id) = graphic.tile_id { + Selected::Tile(id) + } else if let Some(path) = graphic.character_name.clone() { + Selected::Graphic(path) + } else { + Selected::None + }; + Self { entries, open: false, id_source, + selected, tilepicker, @@ -100,23 +117,27 @@ impl luminol_core::Modal for Modal { update_state: &'m mut UpdateState<'_>, ) -> impl egui::Widget + 'm { move |ui: &mut egui::Ui| { - let desired_size = self - .button_sprite - .as_ref() - .map(|s| s.sprite_size) - .unwrap_or(egui::vec2(32., 32.)); - let (response, painter) = ui.allocate_painter(desired_size, egui::Sense::click()); + let desired_size = egui::vec2(64., 96.) + ui.spacing().button_padding * 2.; + let (rect, response) = ui.allocate_at_least(desired_size, egui::Sense::click()); + + let visuals = ui.style().interact_selectable(&response, self.open); + let rect = rect.expand(visuals.expansion); + ui.painter() + .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); if let Some(sprite) = &mut self.button_sprite { - self.button_viewport.set_size( + let translation = (desired_size - sprite.sprite_size) / 2.; + self.button_viewport.set( &update_state.graphics.render_state, glam::vec2(desired_size.x, desired_size.y), + glam::vec2(translation.x, translation.y), + glam::Vec2::ONE, ); let callback = luminol_egui_wgpu::Callback::new_paint_callback( response.rect, Painter::new(sprite.prepare(&update_state.graphics)), ); - painter.add(callback); + ui.painter().add(callback); } if response.clicked() { @@ -152,5 +173,41 @@ impl Modal { ctx: &egui::Context, data: &mut rpg::Graphic, ) { + let mut keep_open = true; + let mut needs_save = false; + + egui::Window::new("Event Graphic Picker") + .resizable(true) + .open(&mut self.open) + .id(self.id_source.with("window")) + .show(ctx, |ui| { + egui::SidePanel::left(self.id_source.with("sidebar")).show_inside(ui, |ui| { + // FIXME: Its better to use show_rows! + egui::ScrollArea::vertical().show(ui, |ui| { + ui.selectable_value(&mut self.selected, Selected::None, "(None)"); + + let checked = matches!(self.selected, Selected::Tile(_)); + let res = ui.selectable_label( + checked, + "(Tileset)", + ); + if res.clicked() && !checked { + self.selected = Selected::Tile(384); + } + for entry in &self.entries { + let checked = + matches!(self.selected, Selected::Graphic(ref path) if path == entry); + if ui.selectable_label(checked, entry.as_str()).clicked() { + self.selected = Selected::Graphic(entry.clone()); + } + } + }); + }); + luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + }); + + if !keep_open { + self.open = false; + } } } diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index 5be281a7..a2557b4f 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -178,11 +178,10 @@ impl luminol_core::Window for Window { ui.horizontal(|ui| { ui.vertical(|ui| { ui.label("Graphic"); - ui.group(|ui| { - self.graphic_modal - .button(&mut page.graphic, update_state) - .ui(ui); - }); + + self.graphic_modal + .button(&mut page.graphic, update_state) + .ui(ui); }); ui.vertical(|ui| { ui.label("Autonomous Movement"); @@ -216,6 +215,7 @@ impl luminol_core::Window for Window { ) .ui(ui); }); + ui.add_space(ui.available_height()); }); }); }); From 76700e5528c3aa37bdd3add18dedebbc33774c38 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 19:33:01 -0700 Subject: [PATCH 11/67] Add sorta working tilepicker --- crates/graphics/src/tilepicker.rs | 2 +- crates/modals/src/event_graphic_picker.rs | 60 ++++++++++++++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/crates/graphics/src/tilepicker.rs b/crates/graphics/src/tilepicker.rs index d47eb2c1..398b8600 100644 --- a/crates/graphics/src/tilepicker.rs +++ b/crates/graphics/src/tilepicker.rs @@ -64,7 +64,7 @@ impl Tilepicker { }; let tilepicker_data = luminol_data::Table3::new_data( 8, - 1 + (atlas.tileset_height / 32) as usize, + !exclude_autotiles as usize + (atlas.tileset_height / 32) as usize, 1, tilepicker_data, ); diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index e52380d0..37b92933 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -70,7 +70,7 @@ impl Modal { &update_state.graphics, tileset, update_state.filesystem, - false, + true, ) .unwrap(); @@ -199,11 +199,67 @@ impl Modal { matches!(self.selected, Selected::Graphic(ref path) if path == entry); if ui.selectable_label(checked, entry.as_str()).clicked() { self.selected = Selected::Graphic(entry.clone()); + self.sprite = None; } } }); }); - luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + + match &mut self.selected { + Selected::None => {} + Selected::Graphic(_) => {} + Selected::Tile(id) => { + egui::ScrollArea::vertical().show_viewport(ui, |ui, viewport| { + let (canvas_rect, response) = ui.allocate_exact_size( + egui::vec2(256., self.tilepicker.atlas.tileset_height as f32), + egui::Sense::click(), + ); + + let absolute_scroll_rect = ui + .ctx() + .screen_rect() + .intersect(viewport.translate(canvas_rect.min.to_vec2())); + let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); + + self.tilepicker.grid.display.set_pixels_per_point( + &update_state.graphics.render_state, + ui.ctx().pixels_per_point(), + ); + + self.tilepicker.set_position( + &update_state.graphics.render_state, + glam::vec2(0.0, -scroll_rect.top()), + ); + self.tilepicker.viewport.set( + &update_state.graphics.render_state, + glam::vec2(scroll_rect.width(), scroll_rect.height()), + glam::Vec2::ZERO, + glam::Vec2::ONE, + ); + + self.tilepicker + .update_animation(&update_state.graphics.render_state, ui.input(|i| i.time)); + + let painter = Painter::new(self.tilepicker.prepare(&update_state.graphics)); + ui.painter() + .add(luminol_egui_wgpu::Callback::new_paint_callback( + absolute_scroll_rect, + painter, + )); + + let tile_x = (*id - 384) % 8; + let tile_y = (*id - 384) / 8; + let rect = egui::Rect::from_min_size(egui::Pos2::new(tile_x as f32, tile_y as f32) * 32., egui::Vec2::splat(32.)).translate(canvas_rect.min.to_vec2()); + ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); + + if response.clicked() { + let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / 32.; + *id = pos.x as usize + pos.y as usize * 8 + 384; + } + }); + } + } + }); if !keep_open { From 9b0bda7afc41b810bce057ef12cd1ae17144c932 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 19:56:53 -0700 Subject: [PATCH 12/67] More picker stuff --- crates/modals/src/event_graphic_picker.rs | 98 +++++++++++++++++++---- 1 file changed, 83 insertions(+), 15 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 37b92933..6eefdc43 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -28,21 +28,36 @@ pub struct Modal { entries: Vec, open: bool, id_source: egui::Id, + selected: Selected, + opacity: i32, + hue: i32, + blend_mode: rpg::BlendMode, + first_open: bool, tilepicker: Tilepicker, button_viewport: Viewport, button_sprite: Option, - sprite: Option<(Viewport, Sprite)>, + sprite: Option, +} + +struct PreviewSprite { + sprite: Sprite, + sprite_size: egui::Vec2, + viewport: Viewport } #[derive(PartialEq)] enum Selected { None, Tile(usize), - Graphic(camino::Utf8PathBuf), + Graphic { + path: camino::Utf8PathBuf, + direction: i32, + pattern: i32, + }, } impl Modal { @@ -84,19 +99,17 @@ impl Modal { ) .unwrap(); - let selected = if let Some(id) = graphic.tile_id { - Selected::Tile(id) - } else if let Some(path) = graphic.character_name.clone() { - Selected::Graphic(path) - } else { - Selected::None - }; Self { entries, open: false, id_source, - selected, + + selected: Selected::None, + opacity: graphic.opacity, + hue: graphic.character_hue, + blend_mode: graphic.blend_type, + first_open: false, tilepicker, @@ -141,6 +154,20 @@ impl luminol_core::Modal for Modal { } if response.clicked() { + + self.selected = if let Some(id) = data.tile_id { + Selected::Tile(id) + } else if let Some(path) = data.character_name.clone() { + Selected::Graphic { path, direction: data.direction, pattern: data.pattern } + } else { + Selected::None + }; + self.blend_mode = data.blend_type; + self.hue = data.character_hue; + self.opacity = data.opacity; + self.first_open = true; + self.sprite = None; + self.open = true; } self.show_window(update_state, ui.ctx(), data); @@ -184,30 +211,69 @@ impl Modal { egui::SidePanel::left(self.id_source.with("sidebar")).show_inside(ui, |ui| { // FIXME: Its better to use show_rows! egui::ScrollArea::vertical().show(ui, |ui| { - ui.selectable_value(&mut self.selected, Selected::None, "(None)"); + let res = ui.selectable_value(&mut self.selected, Selected::None, "(None)"); + if self.first_open && matches!(self.selected, Selected::None) { + res.scroll_to_me(Some(egui::Align::Center)); + } let checked = matches!(self.selected, Selected::Tile(_)); let res = ui.selectable_label( checked, "(Tileset)", ); + + if self.first_open && checked { + res.scroll_to_me(Some(egui::Align::Center)); + } + if res.clicked() && !checked { self.selected = Selected::Tile(384); } + for entry in &self.entries { let checked = - matches!(self.selected, Selected::Graphic(ref path) if path == entry); + matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); if ui.selectable_label(checked, entry.as_str()).clicked() { - self.selected = Selected::Graphic(entry.clone()); + self.selected = Selected::Graphic { path: entry.clone(), direction: 0, pattern: 0 }; self.sprite = None; } + if self.first_open && checked { + res.scroll_to_me(Some(egui::Align::Center)); + } } }); }); match &mut self.selected { Selected::None => {} - Selected::Graphic(_) => {} + Selected::Graphic { path, direction, pattern } => { + let sprite = self.sprite.get_or_insert_with(|| { + let texture = update_state.graphics + .texture_loader + .load_now_dir(update_state.filesystem, "Graphics/Characters", path).unwrap(); + let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2()); + let quad = Quad::new(rect, rect); + let viewport = Viewport::new(&update_state.graphics, glam::vec2(texture.width() as f32, texture.height() as f32)); + + let sprite = Sprite::new(&update_state.graphics, quad, 0, 255, rpg::BlendMode::Normal, &texture, &viewport, Transform::unit(&update_state.graphics)); + PreviewSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport, + } + }); + + let (canvas_rect, response) = ui.allocate_exact_size( + sprite.sprite_size, + egui::Sense::click(), + ); + let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); + ui.painter() + .add(luminol_egui_wgpu::Callback::new_paint_callback( + canvas_rect, + painter, + )); + } Selected::Tile(id) => { egui::ScrollArea::vertical().show_viewport(ui, |ui, viewport| { let (canvas_rect, response) = ui.allocate_exact_size( @@ -239,7 +305,7 @@ impl Modal { self.tilepicker .update_animation(&update_state.graphics.render_state, ui.input(|i| i.time)); - + let painter = Painter::new(self.tilepicker.prepare(&update_state.graphics)); ui.painter() .add(luminol_egui_wgpu::Callback::new_paint_callback( @@ -262,6 +328,8 @@ impl Modal { }); + self.first_open = false; + if !keep_open { self.open = false; } From 735687c5addf185ad1749de2a66084f4b5d45ef7 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 20:11:54 -0700 Subject: [PATCH 13/67] More picker things --- crates/modals/src/event_graphic_picker.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 6eefdc43..bc5114bf 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -234,7 +234,7 @@ impl Modal { let checked = matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); if ui.selectable_label(checked, entry.as_str()).clicked() { - self.selected = Selected::Graphic { path: entry.clone(), direction: 0, pattern: 0 }; + self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0 }; self.sprite = None; } if self.first_open && checked { @@ -273,6 +273,17 @@ impl Modal { canvas_rect, painter, )); + + let ch = sprite.sprite_size.y / 4.; + let cw = sprite.sprite_size.x / 4.; + let rect = egui::Rect::from_min_size(egui::pos2(cw ** pattern as f32, ch * (*direction as f32 - 2.) / 2.), egui::vec2(cw, ch)).translate(canvas_rect.min.to_vec2()); + ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); + + if response.clicked() { + let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / egui::vec2(cw, ch); + *direction = pos.y as i32 * 2 + 2; + *pattern = pos.x as i32; + } } Selected::Tile(id) => { egui::ScrollArea::vertical().show_viewport(ui, |ui, viewport| { From afc35f75de8c0a6cbbcf6bbe7d146bf1704dd66d Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 20:35:32 -0700 Subject: [PATCH 14/67] Hue & Opacity editing --- .../src/primitives/shaders/tilemap.wgsl | 11 +++++++- .../graphics/src/primitives/tiles/display.rs | 21 ++++++++++++++-- crates/graphics/src/primitives/tiles/mod.rs | 20 +++++++++------ .../graphics/src/primitives/tiles/shader.rs | 6 +++++ crates/modals/src/event_graphic_picker.rs | 25 ++++++++++++++++--- 5 files changed, 69 insertions(+), 14 deletions(-) diff --git a/crates/graphics/src/primitives/shaders/tilemap.wgsl b/crates/graphics/src/primitives/shaders/tilemap.wgsl index 5583dc70..2b204fcb 100644 --- a/crates/graphics/src/primitives/shaders/tilemap.wgsl +++ b/crates/graphics/src/primitives/shaders/tilemap.wgsl @@ -1,5 +1,6 @@ #import luminol::gamma as Gamma #import luminol::translation as Trans // 🏳️‍⚧️ +#import luminol::hue as Hue // 🏳️‍⚧️ struct InstanceInput { @location(0) tile_id: u32, @@ -25,6 +26,7 @@ var atlas_sampler: sampler; struct Display { opacity: f32, + hue: f32, map_size: vec2, } @@ -128,9 +130,16 @@ fn fs_main(input: VertexOutput) -> @location(0) vec4 { color.a *= display.opacity; - if color.a <= 0.0 { + if color.a <= 0.001 { discard; } + if display.hue > 0.0 { + var hsv = Hue::rgb_to_hsv(color.rgb); + + hsv.x += display.hue; + color = vec4(Hue::hsv_to_rgb(hsv), color.a); + } + return Gamma::from_linear_rgba(color); } diff --git a/crates/graphics/src/primitives/tiles/display.rs b/crates/graphics/src/primitives/tiles/display.rs index b0805912..75c0a3c4 100644 --- a/crates/graphics/src/primitives/tiles/display.rs +++ b/crates/graphics/src/primitives/tiles/display.rs @@ -42,7 +42,7 @@ struct LayerData { #[derive(Copy, Clone, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] pub struct Data { opacity: f32, - _pad: [u8; 4], + hue: f32, map_size: [u32; 2], } @@ -101,7 +101,7 @@ impl Display { for layer in 0..layers { *layer_data.read_data_at_mut(layer) = Data { opacity: 1.0, - _pad: [0; 4], + hue: 0.0, map_size: [map_width, map_height], }; } @@ -145,6 +145,23 @@ impl Display { } } + pub fn hue(&self, layer: usize) -> f32 { + self.data.read_data_at(layer).hue + } + + pub fn set_hue( + &mut self, + render_state: &luminol_egui_wgpu::RenderState, + hue: f32, + layer: usize, + ) { + let layer_data = self.data.read_data_at_mut(layer); + if layer_data.hue != hue { + layer_data.hue = hue; + self.regen_buffer(render_state, &self.data.data); + } + } + pub fn aligned_layer_size(&self) -> usize { Data::aligned_size_of(self.data.min_alignment_size) } diff --git a/crates/graphics/src/primitives/tiles/mod.rs b/crates/graphics/src/primitives/tiles/mod.rs index fcb50e59..f8d860b6 100644 --- a/crates/graphics/src/primitives/tiles/mod.rs +++ b/crates/graphics/src/primitives/tiles/mod.rs @@ -41,6 +41,7 @@ pub struct Tiles { pub transform: Transform, pub enabled_layers: Vec, pub selected_layer: Option, + pub auto_opacity: bool, instances: Arc, bind_group: Arc, @@ -85,6 +86,7 @@ impl Tiles { transform, enabled_layers: vec![true; tiles.zsize()], selected_layer: None, + auto_opacity: true, instances: Arc::new(instances), bind_group: Arc::new(bind_group), @@ -118,14 +120,16 @@ impl Renderable for Tiles { let graphics_state = Arc::clone(graphics_state); let instances = Arc::clone(&self.instances); - for layer in 0..self.enabled_layers.len() { - let opacity = if self.selected_layer.is_some_and(|s| s != layer) { - 0.5 - } else { - 1.0 - }; - self.display - .set_opacity(&graphics_state.render_state, opacity, layer); + if self.auto_opacity { + for layer in 0..self.enabled_layers.len() { + let opacity = if self.selected_layer.is_some_and(|s| s != layer) { + 0.5 + } else { + 1.0 + }; + self.display + .set_opacity(&graphics_state.render_state, opacity, layer); + } } Prepared { diff --git a/crates/graphics/src/primitives/tiles/shader.rs b/crates/graphics/src/primitives/tiles/shader.rs index a391f187..74382027 100644 --- a/crates/graphics/src/primitives/tiles/shader.rs +++ b/crates/graphics/src/primitives/tiles/shader.rs @@ -35,6 +35,12 @@ pub fn create_render_pipeline( ..Default::default() })?; + composer.add_composable_module(naga_oil::compose::ComposableModuleDescriptor { + source: include_str!("../shaders/hue.wgsl"), + file_path: "hue.wgsl", + ..Default::default() + })?; + let module = composer.make_naga_module(naga_oil::compose::NagaModuleDescriptor { source: include_str!("../shaders/tilemap.wgsl"), file_path: "tilemap.wgsl", diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index bc5114bf..7614e050 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -22,6 +22,7 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. +use egui::Widget; use luminol_core::prelude::*; pub struct Modal { @@ -81,13 +82,14 @@ impl Modal { }) .collect(); - let tilepicker = Tilepicker::new( + let mut tilepicker = Tilepicker::new( &update_state.graphics, tileset, update_state.filesystem, true, ) .unwrap(); + tilepicker.tiles.auto_opacity = false; let button_viewport = Viewport::new(&update_state.graphics, Default::default()); let button_sprite = Event::new_standalone( @@ -243,7 +245,25 @@ impl Modal { } }); }); - + egui::SidePanel::right(self.id_source.with("sidebar_right")).show_inside(ui, |ui| { + ui.label("Opacity"); + if ui.add(egui::Slider::new(&mut self.opacity, 0..=255)).changed() { + self.tilepicker.tiles.display.set_opacity(&update_state.graphics.render_state, self.opacity as f32 / 255., 0); + if let Some(sprite) = &mut self.sprite { + sprite.sprite.graphic.set_opacity(&update_state.graphics.render_state, self.opacity); + } + } + ui.label("Hue"); + if ui.add(egui::Slider::new(&mut self.hue, 0..=360)).changed() { + self.tilepicker.tiles.display.set_hue(&update_state.graphics.render_state, self.hue as f32 / 360.0, 0); + if let Some(sprite) = &mut self.sprite { + sprite.sprite.graphic.set_hue(&update_state.graphics.render_state, self.hue); + } + } + ui.label("Blend Mode"); + luminol_components::EnumComboBox::new(self.id_source.with("blend_mode"), &mut self.blend_mode).ui(ui); + }); + match &mut self.selected { Selected::None => {} Selected::Graphic { path, direction, pattern } => { @@ -336,7 +356,6 @@ impl Modal { }); } } - }); self.first_open = false; From 55e82ddf4cc851974ad06dd208ce626b532c4213 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 20:41:48 -0700 Subject: [PATCH 15/67] Fix some display issues --- crates/graphics/src/tilepicker.rs | 2 +- crates/modals/src/event_graphic_picker.rs | 191 +++++++++++----------- 2 files changed, 99 insertions(+), 94 deletions(-) diff --git a/crates/graphics/src/tilepicker.rs b/crates/graphics/src/tilepicker.rs index 398b8600..66340f60 100644 --- a/crates/graphics/src/tilepicker.rs +++ b/crates/graphics/src/tilepicker.rs @@ -122,7 +122,7 @@ impl Tilepicker { viewport, coll_enabled: false, - grid_enabled: false, + grid_enabled: true, ani_time: None, }) } diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 7614e050..efb4c46b 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -90,6 +90,7 @@ impl Modal { ) .unwrap(); tilepicker.tiles.auto_opacity = false; + let button_viewport = Viewport::new(&update_state.graphics, Default::default()); let button_sprite = Event::new_standalone( @@ -245,6 +246,103 @@ impl Modal { } }); }); + + + egui::CentralPanel::default().show_inside(ui, |ui| { + match &mut self.selected { + Selected::None => {} + Selected::Graphic { path, direction, pattern } => { + let sprite = self.sprite.get_or_insert_with(|| { + let texture = update_state.graphics + .texture_loader + .load_now_dir(update_state.filesystem, "Graphics/Characters", path).unwrap(); + let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2()); + let quad = Quad::new(rect, rect); + let viewport = Viewport::new(&update_state.graphics, glam::vec2(texture.width() as f32, texture.height() as f32)); + + let sprite = Sprite::new(&update_state.graphics, quad, 0, 255, rpg::BlendMode::Normal, &texture, &viewport, Transform::unit(&update_state.graphics)); + PreviewSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport, + } + }); + + let (canvas_rect, response) = ui.allocate_exact_size( + sprite.sprite_size, + egui::Sense::click(), + ); + let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); + ui.painter() + .add(luminol_egui_wgpu::Callback::new_paint_callback( + canvas_rect, + painter, + )); + + let ch = sprite.sprite_size.y / 4.; + let cw = sprite.sprite_size.x / 4.; + let rect = egui::Rect::from_min_size(egui::pos2(cw ** pattern as f32, ch * (*direction as f32 - 2.) / 2.), egui::vec2(cw, ch)).translate(canvas_rect.min.to_vec2()); + ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); + + if response.clicked() { + let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / egui::vec2(cw, ch); + *direction = pos.y as i32 * 2 + 2; + *pattern = pos.x as i32; + } + } + Selected::Tile(id) => { + egui::ScrollArea::vertical().show_viewport(ui, |ui, viewport| { + let (canvas_rect, response) = ui.allocate_exact_size( + egui::vec2(256., self.tilepicker.atlas.tileset_height as f32), + egui::Sense::click(), + ); + + let absolute_scroll_rect = ui + .ctx() + .screen_rect() + .intersect(viewport.translate(canvas_rect.min.to_vec2())); + let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); + + self.tilepicker.grid.display.set_pixels_per_point( + &update_state.graphics.render_state, + ui.ctx().pixels_per_point(), + ); + + self.tilepicker.set_position( + &update_state.graphics.render_state, + glam::vec2(0.0, -scroll_rect.top()), + ); + self.tilepicker.viewport.set( + &update_state.graphics.render_state, + glam::vec2(scroll_rect.width(), scroll_rect.height()), + glam::Vec2::ZERO, + glam::Vec2::ONE, + ); + + self.tilepicker + .update_animation(&update_state.graphics.render_state, ui.input(|i| i.time)); + + let painter = Painter::new(self.tilepicker.prepare(&update_state.graphics)); + ui.painter() + .add(luminol_egui_wgpu::Callback::new_paint_callback( + absolute_scroll_rect, + painter, + )); + + let tile_x = (*id - 384) % 8; + let tile_y = (*id - 384) / 8; + let rect = egui::Rect::from_min_size(egui::Pos2::new(tile_x as f32, tile_y as f32) * 32., egui::Vec2::splat(32.)).translate(canvas_rect.min.to_vec2()); + ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); + + if response.clicked() { + let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / 32.; + *id = pos.x as usize + pos.y as usize * 8 + 384; + } + }); + } + } + }); + egui::SidePanel::right(self.id_source.with("sidebar_right")).show_inside(ui, |ui| { ui.label("Opacity"); if ui.add(egui::Slider::new(&mut self.opacity, 0..=255)).changed() { @@ -263,99 +361,6 @@ impl Modal { ui.label("Blend Mode"); luminol_components::EnumComboBox::new(self.id_source.with("blend_mode"), &mut self.blend_mode).ui(ui); }); - - match &mut self.selected { - Selected::None => {} - Selected::Graphic { path, direction, pattern } => { - let sprite = self.sprite.get_or_insert_with(|| { - let texture = update_state.graphics - .texture_loader - .load_now_dir(update_state.filesystem, "Graphics/Characters", path).unwrap(); - let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2()); - let quad = Quad::new(rect, rect); - let viewport = Viewport::new(&update_state.graphics, glam::vec2(texture.width() as f32, texture.height() as f32)); - - let sprite = Sprite::new(&update_state.graphics, quad, 0, 255, rpg::BlendMode::Normal, &texture, &viewport, Transform::unit(&update_state.graphics)); - PreviewSprite { - sprite, - sprite_size: texture.size_vec2(), - viewport, - } - }); - - let (canvas_rect, response) = ui.allocate_exact_size( - sprite.sprite_size, - egui::Sense::click(), - ); - let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); - ui.painter() - .add(luminol_egui_wgpu::Callback::new_paint_callback( - canvas_rect, - painter, - )); - - let ch = sprite.sprite_size.y / 4.; - let cw = sprite.sprite_size.x / 4.; - let rect = egui::Rect::from_min_size(egui::pos2(cw ** pattern as f32, ch * (*direction as f32 - 2.) / 2.), egui::vec2(cw, ch)).translate(canvas_rect.min.to_vec2()); - ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); - - if response.clicked() { - let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / egui::vec2(cw, ch); - *direction = pos.y as i32 * 2 + 2; - *pattern = pos.x as i32; - } - } - Selected::Tile(id) => { - egui::ScrollArea::vertical().show_viewport(ui, |ui, viewport| { - let (canvas_rect, response) = ui.allocate_exact_size( - egui::vec2(256., self.tilepicker.atlas.tileset_height as f32), - egui::Sense::click(), - ); - - let absolute_scroll_rect = ui - .ctx() - .screen_rect() - .intersect(viewport.translate(canvas_rect.min.to_vec2())); - let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); - - self.tilepicker.grid.display.set_pixels_per_point( - &update_state.graphics.render_state, - ui.ctx().pixels_per_point(), - ); - - self.tilepicker.set_position( - &update_state.graphics.render_state, - glam::vec2(0.0, -scroll_rect.top()), - ); - self.tilepicker.viewport.set( - &update_state.graphics.render_state, - glam::vec2(scroll_rect.width(), scroll_rect.height()), - glam::Vec2::ZERO, - glam::Vec2::ONE, - ); - - self.tilepicker - .update_animation(&update_state.graphics.render_state, ui.input(|i| i.time)); - - let painter = Painter::new(self.tilepicker.prepare(&update_state.graphics)); - ui.painter() - .add(luminol_egui_wgpu::Callback::new_paint_callback( - absolute_scroll_rect, - painter, - )); - - let tile_x = (*id - 384) % 8; - let tile_y = (*id - 384) / 8; - let rect = egui::Rect::from_min_size(egui::Pos2::new(tile_x as f32, tile_y as f32) * 32., egui::Vec2::splat(32.)).translate(canvas_rect.min.to_vec2()); - ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); - - if response.clicked() { - let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / 32.; - *id = pos.x as usize + pos.y as usize * 8 + 384; - } - }); - } - } }); self.first_open = false; From 134701ec06a07a52bea16b8597fccf1a54b025d1 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 21:00:33 -0700 Subject: [PATCH 16/67] adjust layout --- crates/modals/src/event_graphic_picker.rs | 135 ++++++++++++---------- 1 file changed, 73 insertions(+), 62 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index efb4c46b..7499d765 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -247,51 +247,81 @@ impl Modal { }); }); + egui::TopBottomPanel::top(self.id_source.with("bottom")).show_inside(ui, |ui| { + ui.horizontal(|ui| { + ui.label("Opacity"); + if ui.add(egui::Slider::new(&mut self.opacity, 0..=255)).changed() { + self.tilepicker.tiles.display.set_opacity(&update_state.graphics.render_state, self.opacity as f32 / 255., 0); + if let Some(sprite) = &mut self.sprite { + sprite.sprite.graphic.set_opacity(&update_state.graphics.render_state, self.opacity); + } + } + ui.label("Hue"); + if ui.add(egui::Slider::new(&mut self.hue, 0..=360)).changed() { + self.tilepicker.tiles.display.set_hue(&update_state.graphics.render_state, self.hue as f32 / 360.0, 0); + if let Some(sprite) = &mut self.sprite { + sprite.sprite.graphic.set_hue(&update_state.graphics.render_state, self.hue); + } + } + }); + ui.horizontal(|ui| { + ui.label("Blend Mode"); + luminol_components::EnumComboBox::new(self.id_source.with("blend_mode"), &mut self.blend_mode).ui(ui); + }); + }); egui::CentralPanel::default().show_inside(ui, |ui| { - match &mut self.selected { - Selected::None => {} - Selected::Graphic { path, direction, pattern } => { - let sprite = self.sprite.get_or_insert_with(|| { - let texture = update_state.graphics - .texture_loader - .load_now_dir(update_state.filesystem, "Graphics/Characters", path).unwrap(); - let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2()); - let quad = Quad::new(rect, rect); - let viewport = Viewport::new(&update_state.graphics, glam::vec2(texture.width() as f32, texture.height() as f32)); + egui::ScrollArea::both().show_viewport(ui, |ui, viewport| { + match &mut self.selected { + Selected::None => {} + Selected::Graphic { path, direction, pattern } => { + let sprite = self.sprite.get_or_insert_with(|| { + let texture = update_state.graphics + .texture_loader + .load_now_dir(update_state.filesystem, "Graphics/Characters", path).unwrap(); + let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2()); + let quad = Quad::new(rect, rect); + let viewport = Viewport::new(&update_state.graphics, glam::vec2(texture.width() as f32, texture.height() as f32)); + + let sprite = Sprite::new(&update_state.graphics, quad, 0, 255, rpg::BlendMode::Normal, &texture, &viewport, Transform::unit(&update_state.graphics)); + PreviewSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport, + } + }); + + let (canvas_rect, response) = ui.allocate_exact_size( + sprite.sprite_size, + egui::Sense::click(), + ); - let sprite = Sprite::new(&update_state.graphics, quad, 0, 255, rpg::BlendMode::Normal, &texture, &viewport, Transform::unit(&update_state.graphics)); - PreviewSprite { - sprite, - sprite_size: texture.size_vec2(), - viewport, + sprite.viewport.set( + &update_state.graphics.render_state, + glam::vec2(canvas_rect.width(), canvas_rect.height()), + glam::Vec2::ZERO, + glam::Vec2::ONE, + ); + + let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); + ui.painter() + .add(luminol_egui_wgpu::Callback::new_paint_callback( + canvas_rect, + painter, + )); + + let ch = sprite.sprite_size.y / 4.; + let cw = sprite.sprite_size.x / 4.; + let rect = egui::Rect::from_min_size(egui::pos2(cw ** pattern as f32, ch * (*direction as f32 - 2.) / 2.), egui::vec2(cw, ch)).translate(canvas_rect.min.to_vec2()); + ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); + + if response.clicked() { + let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / egui::vec2(cw, ch); + *direction = pos.y as i32 * 2 + 2; + *pattern = pos.x as i32; } - }); - - let (canvas_rect, response) = ui.allocate_exact_size( - sprite.sprite_size, - egui::Sense::click(), - ); - let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); - ui.painter() - .add(luminol_egui_wgpu::Callback::new_paint_callback( - canvas_rect, - painter, - )); - - let ch = sprite.sprite_size.y / 4.; - let cw = sprite.sprite_size.x / 4.; - let rect = egui::Rect::from_min_size(egui::pos2(cw ** pattern as f32, ch * (*direction as f32 - 2.) / 2.), egui::vec2(cw, ch)).translate(canvas_rect.min.to_vec2()); - ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); - - if response.clicked() { - let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / egui::vec2(cw, ch); - *direction = pos.y as i32 * 2 + 2; - *pattern = pos.x as i32; } - } - Selected::Tile(id) => { - egui::ScrollArea::vertical().show_viewport(ui, |ui, viewport| { + Selected::Tile(id) => { let (canvas_rect, response) = ui.allocate_exact_size( egui::vec2(256., self.tilepicker.atlas.tileset_height as f32), egui::Sense::click(), @@ -310,7 +340,7 @@ impl Modal { self.tilepicker.set_position( &update_state.graphics.render_state, - glam::vec2(0.0, -scroll_rect.top()), + glam::vec2(-scroll_rect.left(), -scroll_rect.top()), ); self.tilepicker.viewport.set( &update_state.graphics.render_state, @@ -338,28 +368,9 @@ impl Modal { let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / 32.; *id = pos.x as usize + pos.y as usize * 8 + 384; } - }); - } - } - }); - - egui::SidePanel::right(self.id_source.with("sidebar_right")).show_inside(ui, |ui| { - ui.label("Opacity"); - if ui.add(egui::Slider::new(&mut self.opacity, 0..=255)).changed() { - self.tilepicker.tiles.display.set_opacity(&update_state.graphics.render_state, self.opacity as f32 / 255., 0); - if let Some(sprite) = &mut self.sprite { - sprite.sprite.graphic.set_opacity(&update_state.graphics.render_state, self.opacity); - } - } - ui.label("Hue"); - if ui.add(egui::Slider::new(&mut self.hue, 0..=360)).changed() { - self.tilepicker.tiles.display.set_hue(&update_state.graphics.render_state, self.hue as f32 / 360.0, 0); - if let Some(sprite) = &mut self.sprite { - sprite.sprite.graphic.set_hue(&update_state.graphics.render_state, self.hue); + } } - } - ui.label("Blend Mode"); - luminol_components::EnumComboBox::new(self.id_source.with("blend_mode"), &mut self.blend_mode).ui(ui); + }); }); }); From 90a20cf80dc2bcb9ba1f49ae3556c110483cb0d9 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 21:13:27 -0700 Subject: [PATCH 17/67] Show stripes --- crates/modals/src/event_graphic_picker.rs | 82 ++++++++++++++--------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 7499d765..f0ba903e 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -23,6 +23,7 @@ // Program grant you additional permission to convey the resulting work. use egui::Widget; +use luminol_components::UiExt; use luminol_core::prelude::*; pub struct Modal { @@ -214,40 +215,57 @@ impl Modal { egui::SidePanel::left(self.id_source.with("sidebar")).show_inside(ui, |ui| { // FIXME: Its better to use show_rows! egui::ScrollArea::vertical().show(ui, |ui| { - let res = ui.selectable_value(&mut self.selected, Selected::None, "(None)"); - if self.first_open && matches!(self.selected, Selected::None) { - res.scroll_to_me(Some(egui::Align::Center)); - } - - let checked = matches!(self.selected, Selected::Tile(_)); - let res = ui.selectable_label( - checked, - "(Tileset)", - ); - - if self.first_open && checked { - res.scroll_to_me(Some(egui::Align::Center)); - } - - if res.clicked() && !checked { - self.selected = Selected::Tile(384); - } - - for entry in &self.entries { - let checked = - matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); - if ui.selectable_label(checked, entry.as_str()).clicked() { - self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0 }; - self.sprite = None; - } - if self.first_open && checked { - res.scroll_to_me(Some(egui::Align::Center)); - } + ui.with_stripe(true, |ui| { + ui.horizontal(|ui| { + let res = ui.selectable_value(&mut self.selected, Selected::None, "(None)"); + ui.add_space(ui.available_width()); + + if self.first_open && matches!(self.selected, Selected::None) { + res.scroll_to_me(Some(egui::Align::Center)); + } + }); + }); + + ui.with_stripe(false, |ui| { + ui.horizontal(|ui| { + let checked = matches!(self.selected, Selected::Tile(_)); + let res = ui.selectable_label( + checked, + "(Tileset)", + ); + ui.add_space(ui.available_width()); + + if self.first_open && checked { + res.scroll_to_me(Some(egui::Align::Center)); + } + + if res.clicked() && !checked { + self.selected = Selected::Tile(384); + } + }); + }); + + for (i, entry) in self.entries.iter().enumerate() { + ui.horizontal(|ui| { + ui.with_stripe(i % 2 == 0, |ui| { + let checked = + matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); + let res = ui.selectable_label(checked, entry.as_str()); + ui.add_space(ui.available_width()); + if res.clicked() { + self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0 }; + self.sprite = None; + } + if self.first_open && checked { + res.scroll_to_me(Some(egui::Align::Center)); + } + }); + }); } }); }); - egui::TopBottomPanel::top(self.id_source.with("bottom")).show_inside(ui, |ui| { + egui::TopBottomPanel::top(self.id_source.with("top")).show_inside(ui, |ui| { ui.horizontal(|ui| { ui.label("Opacity"); if ui.add(egui::Slider::new(&mut self.opacity, 0..=255)).changed() { @@ -269,6 +287,10 @@ impl Modal { luminol_components::EnumComboBox::new(self.id_source.with("blend_mode"), &mut self.blend_mode).ui(ui); }); }); + egui::TopBottomPanel::bottom(self.id_source.with("bottom")).show_inside(ui, |ui| { + ui.add_space(ui.style().spacing.item_spacing.y); + luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + }); egui::CentralPanel::default().show_inside(ui, |ui| { egui::ScrollArea::both().show_viewport(ui, |ui, viewport| { From cdb5a16c389410335aebf92ce7dddda5f621b115 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 21:21:29 -0700 Subject: [PATCH 18/67] Add search and fix stripes --- crates/modals/src/database_modal/mod.rs | 4 +++- crates/modals/src/event_graphic_picker.rs | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/crates/modals/src/database_modal/mod.rs b/crates/modals/src/database_modal/mod.rs index c1f7e89c..dac34b78 100644 --- a/crates/modals/src/database_modal/mod.rs +++ b/crates/modals/src/database_modal/mod.rs @@ -153,13 +153,15 @@ where .auto_shrink([false, false]) .max_height(384.) .show(ui, |ui| { + let mut is_faint = false; M::iter(update_state, |iter| { for (id, text) in iter { if matcher.fuzzy(&text, search_text, false).is_none() { continue; } + is_faint = !is_faint; - ui.with_stripe(id % 2 == 0, |ui| { + ui.with_stripe(is_faint, |ui| { ui.horizontal(|ui| { let response = ui.selectable_value(selected_id, id, text); diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index f0ba903e..2d421065 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -31,6 +31,7 @@ pub struct Modal { open: bool, id_source: egui::Id, + search_text: String, selected: Selected, opacity: i32, hue: i32, @@ -109,6 +110,7 @@ impl Modal { open: false, id_source, + search_text: String::new(), selected: Selected::None, opacity: graphic.opacity, hue: graphic.character_hue, @@ -213,6 +215,10 @@ impl Modal { .id(self.id_source.with("window")) .show(ctx, |ui| { egui::SidePanel::left(self.id_source.with("sidebar")).show_inside(ui, |ui| { + egui::TextEdit::singleline(&mut self.search_text) + .hint_text("Search 🔎") + .show(ui); + ui.separator(); // FIXME: Its better to use show_rows! egui::ScrollArea::vertical().show(ui, |ui| { ui.with_stripe(true, |ui| { @@ -245,9 +251,16 @@ impl Modal { }); }); - for (i, entry) in self.entries.iter().enumerate() { + let mut is_faint = false; + let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); + for entry in self.entries.iter() { + if matcher.fuzzy(entry.as_str(), &self.search_text, false).is_none() { + continue; + } + is_faint = !is_faint; + ui.horizontal(|ui| { - ui.with_stripe(i % 2 == 0, |ui| { + ui.with_stripe(is_faint, |ui| { let checked = matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); let res = ui.selectable_label(checked, entry.as_str()); From f005b14e831935fc0e23b3f5d345977aac824a50 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 21:31:12 -0700 Subject: [PATCH 19/67] Actually save the graphic --- crates/modals/src/event_graphic_picker.rs | 39 +++++++++++++++++++---- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 2d421065..17ad73d5 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -52,8 +52,9 @@ struct PreviewSprite { viewport: Viewport } -#[derive(PartialEq)] +#[derive(PartialEq,Default)] enum Selected { + #[default] None, Tile(usize), Graphic { @@ -137,7 +138,7 @@ impl luminol_core::Modal for Modal { ) -> impl egui::Widget + 'm { move |ui: &mut egui::Ui| { let desired_size = egui::vec2(64., 96.) + ui.spacing().button_padding * 2.; - let (rect, response) = ui.allocate_at_least(desired_size, egui::Sense::click()); + let (rect, mut response) = ui.allocate_at_least(desired_size, egui::Sense::click()); let visuals = ui.style().interact_selectable(&response, self.open); let rect = rect.expand(visuals.expansion); @@ -160,7 +161,6 @@ impl luminol_core::Modal for Modal { } if response.clicked() { - self.selected = if let Some(id) = data.tile_id { Selected::Tile(id) } else if let Some(path) = data.character_name.clone() { @@ -176,7 +176,9 @@ impl luminol_core::Modal for Modal { self.open = true; } - self.show_window(update_state, ui.ctx(), data); + if self.show_window(update_state, ui.ctx(), data) { + response.mark_changed(); + } response } @@ -205,7 +207,7 @@ impl Modal { update_state: &luminol_core::UpdateState<'_>, ctx: &egui::Context, data: &mut rpg::Graphic, - ) { + ) -> bool { let mut keep_open = true; let mut needs_save = false; @@ -318,7 +320,7 @@ impl Modal { let quad = Quad::new(rect, rect); let viewport = Viewport::new(&update_state.graphics, glam::vec2(texture.width() as f32, texture.height() as f32)); - let sprite = Sprite::new(&update_state.graphics, quad, 0, 255, rpg::BlendMode::Normal, &texture, &viewport, Transform::unit(&update_state.graphics)); + let sprite = Sprite::new(&update_state.graphics, quad, self.hue, self.opacity, rpg::BlendMode::Normal, &texture, &viewport, Transform::unit(&update_state.graphics)); PreviewSprite { sprite, sprite_size: texture.size_vec2(), @@ -411,8 +413,33 @@ impl Modal { self.first_open = false; + if needs_save { + match std::mem::take(&mut self.selected) { + Selected::None => { + data.tile_id = None; + data.character_name = None; + } + Selected::Tile(id) => { + data.tile_id = Some(id); + data.character_name = None; + } + Selected::Graphic { path, direction, pattern } => { + data.tile_id = None; + data.character_name = Some(path); + data.direction = direction; + data.pattern = pattern; + } + } + data.blend_type = self.blend_mode; + data.character_hue = self.hue; + data.opacity = self.opacity; + self.update_graphic(update_state, data); + } + if !keep_open { self.open = false; } + + needs_save } } From 1459233c21a72f63bc6492a0cf7f8cda11514416 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 22:05:28 -0700 Subject: [PATCH 20/67] Insert events --- crates/components/src/map_view.rs | 17 ++ crates/data/src/shared/event.rs | 1 + crates/ui/src/tabs/map/mod.rs | 2 +- crates/ui/src/tabs/map/util.rs | 2 +- crates/ui/src/windows/event_edit.rs | 301 +++++++++++++++------------- 5 files changed, 181 insertions(+), 142 deletions(-) diff --git a/crates/components/src/map_view.rs b/crates/components/src/map_view.rs index 299f28b7..4cc402df 100644 --- a/crates/components/src/map_view.rs +++ b/crates/components/src/map_view.rs @@ -371,6 +371,23 @@ impl MapView { let mut selected_event_rect = None; for (_, event) in map.events.iter() { + if event.extra_data.modified.get() { + event.extra_data.modified.set(false); + let sprite = luminol_graphics::Event::new_map( + &update_state.graphics, + update_state.filesystem, + &self.map.viewport, + event, + &self.map.atlas, + ) + .unwrap(); // FIXME handle + if let Some(sprite) = sprite { + self.map.events.insert(event.id, sprite); + } else { + self.map.events.remove(event.id); + } + } + let sprite = self.map.events.get_mut(event.id); let has_sprite = sprite.is_some(); let event_size = sprite diff --git a/crates/data/src/shared/event.rs b/crates/data/src/shared/event.rs index 4969ac20..b641afc5 100644 --- a/crates/data/src/shared/event.rs +++ b/crates/data/src/shared/event.rs @@ -40,6 +40,7 @@ pub struct Event { pub struct EventExtraData { /// Whether or not the event editor for this event is open pub is_editor_open: bool, + pub modified: std::cell::Cell, } impl Event { diff --git a/crates/ui/src/tabs/map/mod.rs b/crates/ui/src/tabs/map/mod.rs index 24fe5d32..6be8c922 100644 --- a/crates/ui/src/tabs/map/mod.rs +++ b/crates/ui/src/tabs/map/mod.rs @@ -371,7 +371,7 @@ impl luminol_core::Tab for Tab { let response = self.view.ui( ui, update_state, - &map, + &mut map, &self.tilepicker, self.event_drag_info.is_some(), self.drawing_shape, diff --git a/crates/ui/src/tabs/map/util.rs b/crates/ui/src/tabs/map/util.rs index 4c674780..43b98856 100644 --- a/crates/ui/src/tabs/map/util.rs +++ b/crates/ui/src/tabs/map/util.rs @@ -243,7 +243,7 @@ impl super::Tab { .add_window(crate::windows::event_edit::Window::new( update_state, event, - map.tileset_id, + self.id, tileset, )); Some(new_event_id) diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index a2557b4f..0c204014 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -85,163 +85,184 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - egui::Window::new(self.name()).open(open).show(ctx, |ui| { - let id_source = self.id(); - let previous_page = self.selected_page; - egui::TopBottomPanel::top(id_source.with("top_panel")).show_inside(ui, |ui| { - ui.horizontal(|ui| { - ui.label("Page: "); - for i in 0..self.event.pages.len() { - ui.selectable_value(&mut self.selected_page, i, format!("{}", i + 1)); - } - - if ui - .button(egui::RichText::new("Add").color(egui::Color32::LIGHT_GREEN)) - .clicked() - { - self.event.pages.push(rpg::EventPage::default()); - self.selected_page = self.event.pages.len() - 1; - } - - let button = egui::Button::new( - egui::RichText::new("Delete").color(egui::Color32::LIGHT_RED), - ); - if ui.add_enabled(self.event.pages.len() > 1, button).clicked() { - self.event.pages.remove(self.selected_page); - self.selected_page = self.selected_page.saturating_sub(1); - } - if ui.button(egui::RichText::new("Clear")).clicked() { - self.event.pages[self.selected_page] = rpg::EventPage::default(); - } - }); - }); - - let page = &mut self.event.pages[self.selected_page]; - if self.selected_page != previous_page { - // we need to update the modal to prevent desyncs - self.graphic_modal - .update_graphic(update_state, &page.graphic); - } + let mut win_open = true; + let mut needs_save = false; - egui::SidePanel::left(id_source.with("side_panel")).show_inside(ui, |ui| { - ui.label("Conditions"); - ui.group(|ui| { - ui.horizontal(|ui| { - ui.checkbox(&mut page.condition.switch1_valid, "Switch"); - ui.add_enabled( - page.condition.switch1_valid, - self.switch_1_modal - .button(&mut page.condition.switch1_id, update_state), - ); - ui.label("is ON"); - }); + egui::Window::new(self.name()) + .open(&mut win_open) + .show(ctx, |ui| { + let id_source = self.id(); + let previous_page = self.selected_page; + egui::TopBottomPanel::top(id_source.with("top_panel")).show_inside(ui, |ui| { ui.horizontal(|ui| { - ui.checkbox(&mut page.condition.switch2_valid, "Switch"); - ui.add_enabled( - page.condition.switch2_valid, - self.switch_2_modal - .button(&mut page.condition.switch2_id, update_state), - ); - ui.label("is ON"); - }); - ui.horizontal(|ui| { - ui.checkbox(&mut page.condition.variable_valid, "Variable"); - ui.add_enabled( - page.condition.variable_valid, - self.variable_modal - .button(&mut page.condition.variable_id, update_state), - ); - ui.label("is"); - ui.add_enabled( - page.condition.variable_valid, - egui::DragValue::new(&mut page.condition.variable_value), - ); - ui.label("or above"); - }); - ui.horizontal(|ui| { - ui.checkbox(&mut page.condition.self_switch_valid, "Self Switch"); - // TODO add self switch text box (config option) - ui.add_enabled( - // FIXME ensure shrink - page.condition.self_switch_valid, - luminol_components::EnumMenuButton::new( - &mut page.condition.self_switch_ch, - id_source.with("self_switch_ch"), - ), + ui.label("Page: "); + for i in 0..self.event.pages.len() { + ui.selectable_value(&mut self.selected_page, i, format!("{}", i + 1)); + } + + if ui + .button(egui::RichText::new("Add").color(egui::Color32::LIGHT_GREEN)) + .clicked() + { + self.event.pages.push(rpg::EventPage::default()); + self.selected_page = self.event.pages.len() - 1; + } + + let button = egui::Button::new( + egui::RichText::new("Delete").color(egui::Color32::LIGHT_RED), ); - ui.label("is ON"); - // ensure we expand to fit the side panel - ui.add_space(ui.available_width()); + if ui.add_enabled(self.event.pages.len() > 1, button).clicked() { + self.event.pages.remove(self.selected_page); + self.selected_page = self.selected_page.saturating_sub(1); + } + if ui.button(egui::RichText::new("Clear")).clicked() { + self.event.pages[self.selected_page] = rpg::EventPage::default(); + } }); }); - ui.horizontal(|ui| { - ui.vertical(|ui| { - ui.label("Graphic"); + let page = &mut self.event.pages[self.selected_page]; + if self.selected_page != previous_page { + // we need to update the modal to prevent desyncs + self.graphic_modal + .update_graphic(update_state, &page.graphic); + } - self.graphic_modal - .button(&mut page.graphic, update_state) - .ui(ui); - }); - ui.vertical(|ui| { - ui.label("Autonomous Movement"); - ui.group(|ui| { - // FIXME these expand to fit, which is kinda annoying - ui.horizontal(|ui| { - ui.label("Move Type"); - luminol_components::EnumComboBox::new( - id_source.with("move_type"), - &mut page.move_type, - ) - .ui(ui); - }); + egui::SidePanel::left(id_source.with("side_panel")).show_inside(ui, |ui| { + ui.label("Conditions"); + ui.group(|ui| { + ui.horizontal(|ui| { + ui.checkbox(&mut page.condition.switch1_valid, "Switch"); ui.add_enabled( - page.move_type == luminol_data::rpg::MoveType::Custom, - egui::Button::new("Move Route..."), - ); // TODO - ui.horizontal(|ui| { - ui.label("Move Speed"); - luminol_components::EnumComboBox::new( - id_source.with("move_speed"), - &mut page.move_speed, - ) - .ui(ui); - }); - ui.horizontal(|ui| { - ui.label("Move Frequency"); - luminol_components::EnumComboBox::new( - id_source.with("move_frequency"), - &mut page.move_frequency, - ) + page.condition.switch1_valid, + self.switch_1_modal + .button(&mut page.condition.switch1_id, update_state), + ); + ui.label("is ON"); + }); + ui.horizontal(|ui| { + ui.checkbox(&mut page.condition.switch2_valid, "Switch"); + ui.add_enabled( + page.condition.switch2_valid, + self.switch_2_modal + .button(&mut page.condition.switch2_id, update_state), + ); + ui.label("is ON"); + }); + ui.horizontal(|ui| { + ui.checkbox(&mut page.condition.variable_valid, "Variable"); + ui.add_enabled( + page.condition.variable_valid, + self.variable_modal + .button(&mut page.condition.variable_id, update_state), + ); + ui.label("is"); + ui.add_enabled( + page.condition.variable_valid, + egui::DragValue::new(&mut page.condition.variable_value), + ); + ui.label("or above"); + }); + ui.horizontal(|ui| { + ui.checkbox(&mut page.condition.self_switch_valid, "Self Switch"); + // TODO add self switch text box (config option) + ui.add_enabled( + // FIXME ensure shrink + page.condition.self_switch_valid, + luminol_components::EnumMenuButton::new( + &mut page.condition.self_switch_ch, + id_source.with("self_switch_ch"), + ), + ); + ui.label("is ON"); + // ensure we expand to fit the side panel + ui.add_space(ui.available_width()); + }); + }); + + ui.horizontal(|ui| { + ui.vertical(|ui| { + ui.label("Graphic"); + + self.graphic_modal + .button(&mut page.graphic, update_state) .ui(ui); + }); + ui.vertical(|ui| { + ui.label("Autonomous Movement"); + ui.group(|ui| { + // FIXME these expand to fit, which is kinda annoying + ui.horizontal(|ui| { + ui.label("Move Type"); + luminol_components::EnumComboBox::new( + id_source.with("move_type"), + &mut page.move_type, + ) + .ui(ui); + }); + ui.add_enabled( + page.move_type == luminol_data::rpg::MoveType::Custom, + egui::Button::new("Move Route..."), + ); // TODO + ui.horizontal(|ui| { + ui.label("Move Speed"); + luminol_components::EnumComboBox::new( + id_source.with("move_speed"), + &mut page.move_speed, + ) + .ui(ui); + }); + ui.horizontal(|ui| { + ui.label("Move Frequency"); + luminol_components::EnumComboBox::new( + id_source.with("move_frequency"), + &mut page.move_frequency, + ) + .ui(ui); + }); + ui.add_space(ui.available_height()); }); - ui.add_space(ui.available_height()); }); }); - }); - ui.columns(2, |columns| { - let [left, right] = columns else { - unreachable!() - }; - - left.label("Options"); - left.group(|ui| { - ui.style_mut().wrap = Some(false); - ui.checkbox(&mut page.walk_anime, "Move Animation"); - ui.checkbox(&mut page.step_anime, "Step Animation"); - ui.checkbox(&mut page.direction_fix, "Direction Fix"); - ui.checkbox(&mut page.through, "Through"); - ui.checkbox(&mut page.always_on_top, "Always on Top"); - }); + ui.columns(2, |columns| { + let [left, right] = columns else { + unreachable!() + }; + + left.label("Options"); + left.group(|ui| { + ui.style_mut().wrap = Some(false); + ui.checkbox(&mut page.walk_anime, "Move Animation"); + ui.checkbox(&mut page.step_anime, "Step Animation"); + ui.checkbox(&mut page.direction_fix, "Direction Fix"); + ui.checkbox(&mut page.through, "Through"); + ui.checkbox(&mut page.always_on_top, "Always on Top"); + }); - right.label("Trigger"); - right.group(|ui| { - luminol_components::EnumRadioList::new(&mut page.trigger).ui(ui); + right.label("Trigger"); + right.group(|ui| { + luminol_components::EnumRadioList::new(&mut page.trigger).ui(ui); + }); }); }); + + egui::TopBottomPanel::bottom(id_source.with("bottom_panel")).show_inside( + ui, + |ui| { + ui.add_space(ui.style().spacing.item_spacing.y); + luminol_components::close_options_ui(ui, open, &mut needs_save) + }, + ); }); - }); + + if needs_save { + self.event.extra_data.modified.set(true); + let mut map = update_state.data.get_map(self.map_id); + map.events.insert(self.event.id, self.event.clone()); // don't like the extra clone, but it's necessary + } + + *open &= win_open; } fn requires_filesystem(&self) -> bool { From 6e1d16f21258062cf6c79c5d4b526184387d42a4 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 22:20:40 -0700 Subject: [PATCH 21/67] Minor fixes --- crates/graphics/src/primitives/shaders/sprite.wgsl | 2 +- crates/modals/src/event_graphic_picker.rs | 3 ++- crates/ui/src/windows/event_edit.rs | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/graphics/src/primitives/shaders/sprite.wgsl b/crates/graphics/src/primitives/shaders/sprite.wgsl index 42cc4331..b6d39624 100644 --- a/crates/graphics/src/primitives/shaders/sprite.wgsl +++ b/crates/graphics/src/primitives/shaders/sprite.wgsl @@ -49,7 +49,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { var tex_sample = textureSample(t_diffuse, s_diffuse, in.tex_coords); tex_sample.a *= graphic.opacity * graphic.opacity_multiplier; - if tex_sample.a <= 0. { + if tex_sample.a <= 0.001 { discard; } diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 17ad73d5..5e7cbc1d 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -72,7 +72,7 @@ impl Modal { id_source: egui::Id, ) -> Self { // TODO error handling - let entries = update_state + let mut entries: Vec<_> = update_state .filesystem .read_dir("Graphics/Characters") .unwrap() @@ -84,6 +84,7 @@ impl Modal { .with_extension("") }) .collect(); + entries.sort_unstable(); let mut tilepicker = Tilepicker::new( &update_state.graphics, diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index 0c204014..81c94f64 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -90,10 +90,15 @@ impl luminol_core::Window for Window { egui::Window::new(self.name()) .open(&mut win_open) + .id(self.id()) .show(ctx, |ui| { let id_source = self.id(); let previous_page = self.selected_page; egui::TopBottomPanel::top(id_source.with("top_panel")).show_inside(ui, |ui| { + ui.horizontal(|ui| { + ui.label("Name: "); + ui.text_edit_singleline(&mut self.event.name); + }); ui.horizontal(|ui| { ui.label("Page: "); for i in 0..self.event.pages.len() { From 7caf9ea6eb8bb394032db55cc4950e8686583677 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 22:23:03 -0700 Subject: [PATCH 22/67] Validate texture sizes --- crates/graphics/src/loaders/texture.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/graphics/src/loaders/texture.rs b/crates/graphics/src/loaders/texture.rs index e330f4db..b9dddaaa 100644 --- a/crates/graphics/src/loaders/texture.rs +++ b/crates/graphics/src/loaders/texture.rs @@ -62,6 +62,14 @@ fn load_wgpu_texture_from_path( let file = filesystem.read(path)?; let texture_data = image::load_from_memory(&file)?.to_rgba8(); + if device.limits().max_texture_dimension_2d < texture_data.width().max(texture_data.height()) { + return Err(color_eyre::eyre::eyre!( + "Texture is too large: {}x{}", + texture_data.width(), + texture_data.height() + )); + } + Ok(load_wgpu_texture_from_image( &texture_data, device, From 7006a97bc68fdd6938eb1eb45cf6a8b52eb94ed0 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Fri, 28 Jun 2024 22:28:18 -0700 Subject: [PATCH 23/67] Fix fmt errors --- crates/modals/src/event_graphic_picker.rs | 50 +++++++++++++---------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 5e7cbc1d..5406bdd8 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -49,10 +49,10 @@ pub struct Modal { struct PreviewSprite { sprite: Sprite, sprite_size: egui::Vec2, - viewport: Viewport + viewport: Viewport, } -#[derive(PartialEq,Default)] +#[derive(PartialEq, Default)] enum Selected { #[default] None, @@ -94,7 +94,6 @@ impl Modal { ) .unwrap(); tilepicker.tiles.auto_opacity = false; - let button_viewport = Viewport::new(&update_state.graphics, Default::default()); let button_sprite = Event::new_standalone( @@ -106,7 +105,6 @@ impl Modal { ) .unwrap(); - Self { entries, open: false, @@ -165,7 +163,11 @@ impl luminol_core::Modal for Modal { self.selected = if let Some(id) = data.tile_id { Selected::Tile(id) } else if let Some(path) = data.character_name.clone() { - Selected::Graphic { path, direction: data.direction, pattern: data.pattern } + Selected::Graphic { + path, + direction: data.direction, + pattern: data.pattern, + } } else { Selected::None }; @@ -228,7 +230,7 @@ impl Modal { ui.horizontal(|ui| { let res = ui.selectable_value(&mut self.selected, Selected::None, "(None)"); ui.add_space(ui.available_width()); - + if self.first_open && matches!(self.selected, Selected::None) { res.scroll_to_me(Some(egui::Align::Center)); } @@ -243,11 +245,11 @@ impl Modal { "(Tileset)", ); ui.add_space(ui.available_width()); - + if self.first_open && checked { res.scroll_to_me(Some(egui::Align::Center)); } - + if res.clicked() && !checked { self.selected = Selected::Tile(384); } @@ -307,7 +309,7 @@ impl Modal { ui.add_space(ui.style().spacing.item_spacing.y); luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); }); - + egui::CentralPanel::default().show_inside(ui, |ui| { egui::ScrollArea::both().show_viewport(ui, |ui, viewport| { match &mut self.selected { @@ -320,7 +322,7 @@ impl Modal { let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2()); let quad = Quad::new(rect, rect); let viewport = Viewport::new(&update_state.graphics, glam::vec2(texture.width() as f32, texture.height() as f32)); - + let sprite = Sprite::new(&update_state.graphics, quad, self.hue, self.opacity, rpg::BlendMode::Normal, &texture, &viewport, Transform::unit(&update_state.graphics)); PreviewSprite { sprite, @@ -333,26 +335,26 @@ impl Modal { sprite.sprite_size, egui::Sense::click(), ); - + sprite.viewport.set( &update_state.graphics.render_state, glam::vec2(canvas_rect.width(), canvas_rect.height()), glam::Vec2::ZERO, glam::Vec2::ONE, ); - + let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); ui.painter() .add(luminol_egui_wgpu::Callback::new_paint_callback( canvas_rect, painter, )); - + let ch = sprite.sprite_size.y / 4.; let cw = sprite.sprite_size.x / 4.; let rect = egui::Rect::from_min_size(egui::pos2(cw ** pattern as f32, ch * (*direction as f32 - 2.) / 2.), egui::vec2(cw, ch)).translate(canvas_rect.min.to_vec2()); ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); - + if response.clicked() { let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / egui::vec2(cw, ch); *direction = pos.y as i32 * 2 + 2; @@ -364,18 +366,18 @@ impl Modal { egui::vec2(256., self.tilepicker.atlas.tileset_height as f32), egui::Sense::click(), ); - + let absolute_scroll_rect = ui .ctx() .screen_rect() .intersect(viewport.translate(canvas_rect.min.to_vec2())); let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); - + self.tilepicker.grid.display.set_pixels_per_point( &update_state.graphics.render_state, ui.ctx().pixels_per_point(), ); - + self.tilepicker.set_position( &update_state.graphics.render_state, glam::vec2(-scroll_rect.left(), -scroll_rect.top()), @@ -386,22 +388,22 @@ impl Modal { glam::Vec2::ZERO, glam::Vec2::ONE, ); - + self.tilepicker .update_animation(&update_state.graphics.render_state, ui.input(|i| i.time)); - + let painter = Painter::new(self.tilepicker.prepare(&update_state.graphics)); ui.painter() .add(luminol_egui_wgpu::Callback::new_paint_callback( absolute_scroll_rect, painter, )); - + let tile_x = (*id - 384) % 8; let tile_y = (*id - 384) / 8; let rect = egui::Rect::from_min_size(egui::Pos2::new(tile_x as f32, tile_y as f32) * 32., egui::Vec2::splat(32.)).translate(canvas_rect.min.to_vec2()); ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); - + if response.clicked() { let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / 32.; *id = pos.x as usize + pos.y as usize * 8 + 384; @@ -424,7 +426,11 @@ impl Modal { data.tile_id = Some(id); data.character_name = None; } - Selected::Graphic { path, direction, pattern } => { + Selected::Graphic { + path, + direction, + pattern, + } => { data.tile_id = None; data.character_name = Some(path); data.direction = direction; From 7415a6b3ff88215632c5a349b45ed911321e790f Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sat, 29 Jun 2024 00:58:07 -0700 Subject: [PATCH 24/67] Make preview sprite loading more fallible --- Cargo.lock | 1 + Cargo.toml | 2 +- crates/modals/Cargo.toml | 1 + crates/modals/src/event_graphic_picker.rs | 116 +++++++++++++++++----- crates/ui/src/tabs/map/mod.rs | 2 +- crates/ui/src/windows/event_edit.rs | 2 +- 6 files changed, 97 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea9b8437..76738f47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3211,6 +3211,7 @@ name = "luminol-modals" version = "0.4.0" dependencies = [ "camino", + "color-eyre", "egui", "fuzzy-matcher", "glam", diff --git a/Cargo.toml b/Cargo.toml index a3a5e2c4..b76a468f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -258,7 +258,7 @@ ProductName = "Luminol" [profile.release] opt-level = 3 # lto = "fat" -# debug = true +debug = true # Enable only a small amount of optimization in debug mode [profile.dev] diff --git a/crates/modals/Cargo.toml b/crates/modals/Cargo.toml index 31fb90cf..4dff7cfd 100644 --- a/crates/modals/Cargo.toml +++ b/crates/modals/Cargo.toml @@ -26,6 +26,7 @@ luminol-data.workspace = true luminol-components.workspace = true luminol-egui-wgpu.workspace = true glam.workspace = true +color-eyre.workspace = true fuzzy-matcher = "0.3.7" diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 5406bdd8..fc8f8e35 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -22,6 +22,7 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. +use color_eyre::eyre::Context; use egui::Widget; use luminol_components::UiExt; use luminol_core::prelude::*; @@ -52,13 +53,14 @@ struct PreviewSprite { viewport: Viewport, } -#[derive(PartialEq, Default)] +#[derive(Default)] enum Selected { #[default] None, Tile(usize), Graphic { path: camino::Utf8PathBuf, + sprite: PreviewSprite, direction: i32, pattern: i32, }, @@ -163,10 +165,30 @@ impl luminol_core::Modal for Modal { self.selected = if let Some(id) = data.tile_id { Selected::Tile(id) } else if let Some(path) = data.character_name.clone() { + let sprite = match Self::load_preview_sprite( + update_state, + &path, + data.character_hue, + data.opacity, + ) { + Ok(sprite) => sprite, + Err(e) => { + luminol_core::error!(update_state.toasts, e); + let placeholder = + update_state.graphics.texture_loader.placeholder_texture(); + Self::create_preview_sprite_from_texture( + update_state, + &placeholder, + data.character_hue, + data.opacity, + ) + } + }; Selected::Graphic { path, direction: data.direction, pattern: data.pattern, + sprite, } } else { Selected::None @@ -205,9 +227,59 @@ impl Modal { self.sprite = None; } + fn load_preview_sprite( + update_state: &luminol_core::UpdateState<'_>, + path: &camino::Utf8Path, + hue: i32, + opacity: i32, + ) -> color_eyre::Result { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, "Graphics/Characters", path) + .wrap_err("While loading a preview sprite")?; + + Ok(Self::create_preview_sprite_from_texture( + update_state, + &texture, + hue, + opacity, + )) + } + + fn create_preview_sprite_from_texture( + update_state: &luminol_core::UpdateState<'_>, + texture: &Texture, + hue: i32, + opacity: i32, + ) -> PreviewSprite { + let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2()); + let quad = Quad::new(rect, rect); + let viewport = Viewport::new( + &update_state.graphics, + glam::vec2(texture.width() as f32, texture.height() as f32), + ); + + let sprite = Sprite::new( + &update_state.graphics, + quad, + hue, + opacity, + rpg::BlendMode::Normal, + texture, + &viewport, + Transform::unit(&update_state.graphics), + ); + PreviewSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport, + } + } + fn show_window( &mut self, - update_state: &luminol_core::UpdateState<'_>, + update_state: &mut luminol_core::UpdateState<'_>, ctx: &egui::Context, data: &mut rpg::Graphic, ) -> bool { @@ -228,12 +300,16 @@ impl Modal { egui::ScrollArea::vertical().show(ui, |ui| { ui.with_stripe(true, |ui| { ui.horizontal(|ui| { - let res = ui.selectable_value(&mut self.selected, Selected::None, "(None)"); + let res = ui.selectable_label(matches!(self.selected, Selected::None), "(None)"); ui.add_space(ui.available_width()); if self.first_open && matches!(self.selected, Selected::None) { res.scroll_to_me(Some(egui::Align::Center)); } + + if res.clicked() && !matches!(self.selected, Selected::None) { + self.selected = Selected::None; + } }); }); @@ -271,7 +347,14 @@ impl Modal { let res = ui.selectable_label(checked, entry.as_str()); ui.add_space(ui.available_width()); if res.clicked() { - self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0 }; + let sprite = match Self::load_preview_sprite(update_state, entry, self.hue, self.opacity) { + Ok(sprite) => sprite, + Err(e) => { + luminol_core::error!(update_state.toasts, e); + return; + } + }; + self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0, sprite }; self.sprite = None; } if self.first_open && checked { @@ -314,23 +397,7 @@ impl Modal { egui::ScrollArea::both().show_viewport(ui, |ui, viewport| { match &mut self.selected { Selected::None => {} - Selected::Graphic { path, direction, pattern } => { - let sprite = self.sprite.get_or_insert_with(|| { - let texture = update_state.graphics - .texture_loader - .load_now_dir(update_state.filesystem, "Graphics/Characters", path).unwrap(); - let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2()); - let quad = Quad::new(rect, rect); - let viewport = Viewport::new(&update_state.graphics, glam::vec2(texture.width() as f32, texture.height() as f32)); - - let sprite = Sprite::new(&update_state.graphics, quad, self.hue, self.opacity, rpg::BlendMode::Normal, &texture, &viewport, Transform::unit(&update_state.graphics)); - PreviewSprite { - sprite, - sprite_size: texture.size_vec2(), - viewport, - } - }); - + Selected::Graphic { direction, pattern, sprite, .. } => { let (canvas_rect, response) = ui.allocate_exact_size( sprite.sprite_size, egui::Sense::click(), @@ -417,7 +484,7 @@ impl Modal { self.first_open = false; if needs_save { - match std::mem::take(&mut self.selected) { + match self.selected { Selected::None => { data.tile_id = None; data.character_name = None; @@ -427,12 +494,13 @@ impl Modal { data.character_name = None; } Selected::Graphic { - path, + ref path, direction, pattern, + .. } => { data.tile_id = None; - data.character_name = Some(path); + data.character_name = Some(path.clone()); data.direction = direction; data.pattern = pattern; } diff --git a/crates/ui/src/tabs/map/mod.rs b/crates/ui/src/tabs/map/mod.rs index 6be8c922..24fe5d32 100644 --- a/crates/ui/src/tabs/map/mod.rs +++ b/crates/ui/src/tabs/map/mod.rs @@ -371,7 +371,7 @@ impl luminol_core::Tab for Tab { let response = self.view.ui( ui, update_state, - &mut map, + &map, &self.tilepicker, self.event_drag_info.is_some(), self.drawing_shape, diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index 81c94f64..ae9c1ac2 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -239,7 +239,7 @@ impl luminol_core::Window for Window { left.group(|ui| { ui.style_mut().wrap = Some(false); ui.checkbox(&mut page.walk_anime, "Move Animation"); - ui.checkbox(&mut page.step_anime, "Step Animation"); + ui.checkbox(&mut page.step_anime, "Stop Animation"); ui.checkbox(&mut page.direction_fix, "Direction Fix"); ui.checkbox(&mut page.through, "Through"); ui.checkbox(&mut page.always_on_top, "Always on Top"); From a88e2e330068eef4296df91e0e06df6a80c42599 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sat, 29 Jun 2024 01:02:48 -0700 Subject: [PATCH 25/67] Grey out invalid sprites --- crates/modals/src/event_graphic_picker.rs | 26 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index fc8f8e35..4f19a7db 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -28,7 +28,7 @@ use luminol_components::UiExt; use luminol_core::prelude::*; pub struct Modal { - entries: Vec, + entries: Vec, open: bool, id_source: egui::Id, @@ -47,6 +47,12 @@ pub struct Modal { sprite: Option, } +#[derive(PartialEq, PartialOrd, Eq, Ord)] +struct Entry { + path: camino::Utf8PathBuf, + invalid: bool, +} + struct PreviewSprite { sprite: Sprite, sprite_size: egui::Vec2, @@ -80,10 +86,15 @@ impl Modal { .unwrap() .into_iter() .map(|m| { - m.path + let path = m + .path .strip_prefix("Graphics/Characters") .unwrap_or(&m.path) - .with_extension("") + .with_extension(""); + Entry { + path, + invalid: false, + } }) .collect(); entries.sort_unstable(); @@ -334,7 +345,7 @@ impl Modal { let mut is_faint = false; let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); - for entry in self.entries.iter() { + for Entry { path: entry ,invalid} in self.entries.iter_mut() { if matcher.fuzzy(entry.as_str(), &self.search_text, false).is_none() { continue; } @@ -344,13 +355,18 @@ impl Modal { ui.with_stripe(is_faint, |ui| { let checked = matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); - let res = ui.selectable_label(checked, entry.as_str()); + let mut text = egui::RichText::new(entry.as_str()); + if *invalid { + text = text.color(egui::Color32::LIGHT_RED); + } + let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); ui.add_space(ui.available_width()); if res.clicked() { let sprite = match Self::load_preview_sprite(update_state, entry, self.hue, self.opacity) { Ok(sprite) => sprite, Err(e) => { luminol_core::error!(update_state.toasts, e); + *invalid = true; return; } }; From f11243c003983e6f90be92439e0aed770c38db8b Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sat, 29 Jun 2024 01:14:12 -0700 Subject: [PATCH 26/67] Fix preview sprite handling --- crates/modals/src/event_graphic_picker.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 4f19a7db..4da50a00 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -43,8 +43,6 @@ pub struct Modal { button_viewport: Viewport, button_sprite: Option, - - sprite: Option, } #[derive(PartialEq, PartialOrd, Eq, Ord)] @@ -134,8 +132,6 @@ impl Modal { button_viewport, button_sprite, - - sprite: None, } } } @@ -208,7 +204,6 @@ impl luminol_core::Modal for Modal { self.hue = data.character_hue; self.opacity = data.opacity; self.first_open = true; - self.sprite = None; self.open = true; } @@ -235,7 +230,6 @@ impl Modal { &self.tilepicker.atlas, ) .unwrap(); - self.sprite = None; } fn load_preview_sprite( @@ -371,7 +365,6 @@ impl Modal { } }; self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0, sprite }; - self.sprite = None; } if self.first_open && checked { res.scroll_to_me(Some(egui::Align::Center)); @@ -387,14 +380,14 @@ impl Modal { ui.label("Opacity"); if ui.add(egui::Slider::new(&mut self.opacity, 0..=255)).changed() { self.tilepicker.tiles.display.set_opacity(&update_state.graphics.render_state, self.opacity as f32 / 255., 0); - if let Some(sprite) = &mut self.sprite { + if let Selected::Graphic { sprite,.. } = &mut self.selected { sprite.sprite.graphic.set_opacity(&update_state.graphics.render_state, self.opacity); } } ui.label("Hue"); if ui.add(egui::Slider::new(&mut self.hue, 0..=360)).changed() { self.tilepicker.tiles.display.set_hue(&update_state.graphics.render_state, self.hue as f32 / 360.0, 0); - if let Some(sprite) = &mut self.sprite { + if let Selected::Graphic { sprite,.. } = &mut self.selected { sprite.sprite.graphic.set_hue(&update_state.graphics.render_state, self.hue); } } From e64d80a40c2dff0bbcdd705b675b5d8a0e37f1f3 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sat, 29 Jun 2024 01:27:47 -0700 Subject: [PATCH 27/67] Properly handle sprite scrolling --- crates/modals/src/event_graphic_picker.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 4da50a00..d074e845 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -412,9 +412,19 @@ impl Modal { egui::Sense::click(), ); + let absolute_scroll_rect = ui + .ctx() + .screen_rect() + .intersect(viewport.translate(canvas_rect.min.to_vec2())); + let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); + sprite.sprite.transform.set_position( + &update_state.graphics.render_state, + glam::vec2(-scroll_rect.left(), -scroll_rect.top()), + ); + sprite.viewport.set( &update_state.graphics.render_state, - glam::vec2(canvas_rect.width(), canvas_rect.height()), + glam::vec2(absolute_scroll_rect.width(), absolute_scroll_rect.height()), glam::Vec2::ZERO, glam::Vec2::ONE, ); @@ -422,7 +432,7 @@ impl Modal { let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); ui.painter() .add(luminol_egui_wgpu::Callback::new_paint_callback( - canvas_rect, + absolute_scroll_rect, painter, )); From f34e61ad13f64262f4809462646a0308b06837ef Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sat, 29 Jun 2024 01:50:04 -0700 Subject: [PATCH 28/67] Load from RTP on native too --- crates/core/src/lib.rs | 3 +-- crates/filesystem/src/project.rs | 24 ++++++++++++++++++++++-- crates/graphics/src/loaders/texture.rs | 10 ++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 7a0c4a37..d29e5b92 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -355,11 +355,10 @@ impl<'res> UpdateState<'res> { self.toasts, format!("Failed to find suitable path for the RTP {missing_rtp}") ); - // FIXME we should probably load rtps from the RTP/ paths on non-wasm targets #[cfg(not(target_arch = "wasm32"))] info!( self.toasts, - format!("You may want to set an RTP path for {missing_rtp}") + format!("You may want to set an RTP path for {missing_rtp} (you can place it in the RTP folder)") ); #[cfg(target_arch = "wasm32")] info!(self.toasts, format!("Please place the {missing_rtp} RTP in the 'RTP/{missing_rtp}' subdirectory in your project directory")); diff --git a/crates/filesystem/src/project.rs b/crates/filesystem/src/project.rs index 3945bcfd..f716163f 100644 --- a/crates/filesystem/src/project.rs +++ b/crates/filesystem/src/project.rs @@ -240,6 +240,7 @@ impl FileSystem { #[cfg(windows)] impl FileSystem { fn find_rtp_paths( + filesystem: &host::FileSystem, config: &luminol_config::project::Config, global_config: &luminol_config::global::Config, ) -> (Vec, Vec) { @@ -293,6 +294,14 @@ impl FileSystem { } } + let path = camino::Utf8PathBuf::from("RTP").join(rtp); + if let Ok(exists) = filesystem.exists(&path) { + if exists { + paths.push(path); + continue; + } + } + if let Some(path) = global_config.rtp_paths.get(rtp) { let path = camino::Utf8PathBuf::from(path); if path.exists() { @@ -312,6 +321,7 @@ impl FileSystem { #[cfg(not(any(windows, target_arch = "wasm32")))] impl FileSystem { fn find_rtp_paths( + filesystem: &host::FileSystem, config: &luminol_config::project::Config, global_config: &luminol_config::global::Config, ) -> (Vec, Vec) { @@ -337,6 +347,14 @@ impl FileSystem { } } + let path = camino::Utf8PathBuf::from("RTP").join(rtp); + if let Ok(exists) = filesystem.exists(&path) { + if exists { + paths.push(path); + continue; + } + } + missing_rtps.push(rtp.to_string()); } } @@ -380,9 +398,11 @@ impl FileSystem { .map(archiver::FileSystem::new) .transpose()?; - list.push(host); // FIXME: handle missing rtps - let (found_rtps, missing_rtps) = Self::find_rtp_paths(project_config, global_config); + let (found_rtps, missing_rtps) = Self::find_rtp_paths(&host, project_config, global_config); + + list.push(host); + for path in found_rtps { list.push(host::FileSystem::new(path)) } diff --git a/crates/graphics/src/loaders/texture.rs b/crates/graphics/src/loaders/texture.rs index b9dddaaa..0d0efefc 100644 --- a/crates/graphics/src/loaders/texture.rs +++ b/crates/graphics/src/loaders/texture.rs @@ -269,3 +269,13 @@ impl Loader { &self.placeholder_image } } + +// can't use Arc because of orphan rule, must use &instead (this does allow for for &Texture to be used in Image::new tho) +impl From<&Texture> for egui::load::SizedTexture { + fn from(val: &Texture) -> Self { + egui::load::SizedTexture { + id: val.texture_id, + size: val.size_vec2(), + } + } +} From f671112ae33a173480fe37feb220c23781746c72 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sat, 29 Jun 2024 02:00:54 -0700 Subject: [PATCH 29/67] Properly load RTPs this time --- crates/filesystem/src/project.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/filesystem/src/project.rs b/crates/filesystem/src/project.rs index f716163f..da6e7321 100644 --- a/crates/filesystem/src/project.rs +++ b/crates/filesystem/src/project.rs @@ -294,7 +294,7 @@ impl FileSystem { } } - let path = camino::Utf8PathBuf::from("RTP").join(rtp); + let path = filesystem.root_path().join("RTP").join(rtp); if let Ok(exists) = filesystem.exists(&path) { if exists { paths.push(path); @@ -347,7 +347,7 @@ impl FileSystem { } } - let path = camino::Utf8PathBuf::from("RTP").join(rtp); + let path = filesystem.root_path().join("RTP").join(rtp); if let Ok(exists) = filesystem.exists(&path) { if exists { paths.push(path); From 3f4b48439be696ab1d015fe84585e83c6b27220a Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sat, 29 Jun 2024 02:14:38 -0700 Subject: [PATCH 30/67] Don't enable debug symbols in release --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b76a468f..a3a5e2c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -258,7 +258,7 @@ ProductName = "Luminol" [profile.release] opt-level = 3 # lto = "fat" -debug = true +# debug = true # Enable only a small amount of optimization in debug mode [profile.dev] From 5f2fb98115a732ecd3185686c48ed1191a7272f0 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sat, 29 Jun 2024 02:20:04 -0700 Subject: [PATCH 31/67] Remove all nightly features --- crates/components/src/id_vec.rs | 6 +++--- crates/components/src/lib.rs | 1 - crates/core/src/lib.rs | 7 +++++++ crates/data/src/lib.rs | 1 - crates/ui/src/lib.rs | 1 - crates/ui/src/windows/actors.rs | 4 ++-- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/components/src/id_vec.rs b/crates/components/src/id_vec.rs index b43b7e80..db564be9 100644 --- a/crates/components/src/id_vec.rs +++ b/crates/components/src/id_vec.rs @@ -152,7 +152,7 @@ where F: Fn(usize) -> String, { fn ui(self, ui: &mut egui::Ui) -> egui::Response { - if self.search_needs_update && !self.reference.is_sorted() { + if self.search_needs_update && luminol_core::slice_is_sorted(self.reference) { self.reference.sort_unstable(); } @@ -304,10 +304,10 @@ where { fn ui(self, ui: &mut egui::Ui) -> egui::Response { if self.search_needs_update { - if !self.plus.is_sorted() { + if !luminol_core::slice_is_sorted(self.plus) { self.plus.sort_unstable(); } - if !self.minus.is_sorted() { + if !luminol_core::slice_is_sorted(self.minus) { self.minus.sort_unstable(); } } diff --git a/crates/components/src/lib.rs b/crates/components/src/lib.rs index d9890e4b..02a6384b 100644 --- a/crates/components/src/lib.rs +++ b/crates/components/src/lib.rs @@ -22,7 +22,6 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. #![cfg_attr(target_arch = "wasm32", allow(clippy::arc_with_non_send_sync))] -#![feature(is_sorted)] use itertools::Itertools; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index d29e5b92..0ee87f6f 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -436,3 +436,10 @@ impl<'res> UpdateState<'res> { self.modified.set(false); } } + +pub fn slice_is_sorted(s: &[T]) -> bool { + s.windows(2).all(|w| { + let [a, b] = w else { unreachable!() }; // could maybe do unreachable_unchecked + a <= b + }) +} diff --git a/crates/data/src/lib.rs b/crates/data/src/lib.rs index a9712116..408da518 100644 --- a/crates/data/src/lib.rs +++ b/crates/data/src/lib.rs @@ -1,4 +1,3 @@ -#![feature(min_specialization)] #![allow(non_upper_case_globals)] // Editor specific types diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index b53958b9..7f5cdcf2 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -21,7 +21,6 @@ // 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. -#![feature(is_sorted)] pub type UpdateState<'res> = luminol_core::UpdateState<'res>; diff --git a/crates/ui/src/windows/actors.rs b/crates/ui/src/windows/actors.rs index f3ba35c2..dbe35e03 100644 --- a/crates/ui/src/windows/actors.rs +++ b/crates/ui/src/windows/actors.rs @@ -277,10 +277,10 @@ impl luminol_core::Window for Window { }); if let Some(class) = classes.data.get_mut(actor.class_id) { - if !class.weapon_set.is_sorted() { + if !luminol_core::slice_is_sorted(&class.weapon_set) { class.weapon_set.sort_unstable(); } - if !class.armor_set.is_sorted() { + if !luminol_core::slice_is_sorted(&class.armor_set) { class.armor_set.sort_unstable(); } } From 6cb705eca12a542f9331ee3b9c77b66e79707e79 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sat, 29 Jun 2024 02:43:17 -0700 Subject: [PATCH 32/67] Update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d7967545..2ea5ddc9 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ If you're on Linux at the moment for best performance you'll need Chrome canary Native builds are the main focus at the moment, but no official releases will be made until Luminol is stable. If you want to test out Luminol anyway, you can grab a build from [our build workflow](https://github.com/Astrabit-ST/Luminol/actions/workflows/build.yml). -We're working on a website where you'll eventually be able to use the latest development build of Luminol! +It's currently WIP, but there's [a website](https://luminol.dev) where you can try the latest development build of Luminol! If you'd like to compile luminol yourself, you can by grabbing your favorite nightly rust toolchain from [rustup](https://rustup.rs) and running `cargo build`. Additionally, to enable steamworks support pass `--features steamworks` to `cargo build`. @@ -63,9 +63,9 @@ We've also turned on the unstable `-Z threads=8` compiler flag to speed up build This is a pretty unstable feature at the moment and may cause compiler deadlocks. Luckily cargo will detect when that happens and halt your build. Re-running `cargo build` continue your build without issue, though. -Luminol has like a bajillion dependencies right now so it may take upwards of 15 minutes to compile. +Luminol has like a bajillion dependencies right now so it may take upwards of 15 minutes to compile! -**You can not use one of the stable release channels.** +Luminol's native build currently can compile on stable Rust, however we pin the toolchain to nightly for wasm32 and the aforementioned `-Z threads=8` flag. ## Credits From 9e06c96230a88fc66296820510c0708155d2a662 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 08:17:38 -0700 Subject: [PATCH 33/67] Clean up soundtab --- Cargo.lock | 1 + crates/audio/src/lib.rs | 11 ++ crates/components/Cargo.toml | 1 + crates/components/src/sound_tab.rs | 181 ++++++++++++---------- crates/core/src/modal.rs | 1 + crates/modals/src/event_graphic_picker.rs | 1 + crates/modals/src/sound_picker.rs | 65 +++++++- crates/ui/src/windows/sound_test.rs | 2 +- 8 files changed, 181 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76738f47..5a1d693f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2999,6 +2999,7 @@ dependencies = [ name = "luminol-components" version = "0.4.0" dependencies = [ + "camino", "color-eyre", "egui", "fragile", diff --git a/crates/audio/src/lib.rs b/crates/audio/src/lib.rs index 967028e9..4fa8b634 100644 --- a/crates/audio/src/lib.rs +++ b/crates/audio/src/lib.rs @@ -180,3 +180,14 @@ impl Audio { } } } + +impl Source { + pub fn as_path(&self) -> &camino::Utf8Path { + camino::Utf8Path::new(match self { + Source::BGM => "BGM", + Source::BGS => "BGS", + Source::ME => "ME", + Source::SE => "SE", + }) + } +} diff --git a/crates/components/Cargo.toml b/crates/components/Cargo.toml index b05541e1..bb27c0fc 100644 --- a/crates/components/Cargo.toml +++ b/crates/components/Cargo.toml @@ -52,3 +52,4 @@ fuzzy-matcher = "0.3.7" murmur3.workspace = true image.workspace = true +camino.workspace = true diff --git a/crates/components/src/sound_tab.rs b/crates/components/src/sound_tab.rs index 7c2fa363..00aab975 100644 --- a/crates/components/src/sound_tab.rs +++ b/crates/components/src/sound_tab.rs @@ -25,10 +25,9 @@ pub struct SoundTab { /// The source for this tab. pub source: luminol_audio::Source, - pub volume: u8, - pub pitch: u8, - pub selected_track: String, + pub audio_file: luminol_data::rpg::AudioFile, folder_children: Vec, + search_text: String, } impl SoundTab { @@ -36,14 +35,39 @@ impl SoundTab { pub fn new( filesystem: &impl luminol_filesystem::FileSystem, source: luminol_audio::Source, + audio_file: luminol_data::rpg::AudioFile, ) -> Self { - let folder_children = filesystem.read_dir(format!("Audio/{source}")).unwrap(); + let mut folder_children = filesystem.read_dir(format!("Audio/{source}")).unwrap(); + folder_children.sort_unstable_by(|a, b| a.file_name().cmp(b.file_name())); Self { source, - volume: 100, - pitch: 100, - selected_track: String::new(), + audio_file, folder_children, + search_text: String::new(), + } + } + + fn play(&self, update_state: &mut luminol_core::UpdateState<'_>) { + if let Some(track) = &self.audio_file.name { + let path = camino::Utf8Path::new("Audio") + .join(self.source.as_path()) + .join(track); + let pitch = self.audio_file.pitch; + let volume = self.audio_file.volume; + let source = self.source; + + if let Err(e) = + update_state + .audio + .play(path, update_state.filesystem, volume, pitch, source) + { + luminol_core::error!( + update_state.toasts, + e.wrap_err("Error playing from audio file") + ); + } + } else { + update_state.audio.stop(&self.source); } } @@ -54,25 +78,8 @@ impl SoundTab { .show_inside(ui, |ui| { ui.vertical(|ui| { ui.horizontal(|ui| { - if ui.button("Play").clicked() && !self.selected_track.is_empty() { - let path = format!("Audio/{}/{}", self.source, &self.selected_track); - let pitch = self.pitch; - let volume = self.volume; - let source = self.source; - // Play it. - - if let Err(e) = update_state.audio.play( - path, - update_state.filesystem, - volume, - pitch, - source, - ) { - luminol_core::error!( - update_state.toasts, - e.wrap_err("Error playing from audio file") - ); - } + if ui.button("Play").clicked() { + self.play(update_state); } if ui.button("Stop").clicked() { @@ -82,29 +89,33 @@ impl SoundTab { }); ui.horizontal(|ui| { + let step = ui + .input(|i| i.modifiers.shift) + .then_some(5.0) + .unwrap_or_default(); + + let slider = egui::Slider::new(&mut self.audio_file.volume, 0..=100) + .orientation(egui::SliderOrientation::Vertical) + .step_by(step) + .text("Volume"); // Add a slider. // If it's changed, update the volume. - if ui - .add( - egui::Slider::new(&mut self.volume, 0..=100) - .orientation(egui::SliderOrientation::Vertical) - .text("Volume"), - ) - .changed() - { - update_state.audio.set_volume(self.volume, &self.source); + if ui.add(slider).changed() { + update_state + .audio + .set_volume(self.audio_file.volume, &self.source); }; + + let slider = egui::Slider::new(&mut self.audio_file.pitch, 50..=150) + .orientation(egui::SliderOrientation::Vertical) + .step_by(step) + .text("Pitch"); // Add a pitch slider. // If it's changed, update the pitch. - if ui - .add( - egui::Slider::new(&mut self.pitch, 50..=150) - .orientation(egui::SliderOrientation::Vertical) - .text("Pitch"), - ) - .changed() - { - update_state.audio.set_pitch(self.pitch, &self.source); + if ui.add(slider).changed() { + update_state + .audio + .set_pitch(self.audio_file.pitch, &self.source); }; }); }); @@ -112,56 +123,70 @@ impl SoundTab { egui::CentralPanel::default().show_inside(ui, |ui| { // Get row height. - let row_height = ui.text_style_height(&egui::TextStyle::Body); + let row_height = ui.text_style_height(&egui::TextStyle::Body); // i do not trust this + + // FIXME: CACHE THIS! + let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); + let filtered_childen = self + .folder_children + .iter() + .filter(|entry| { + matcher + .fuzzy(entry.file_name(), &self.search_text, false) + .is_some() + }) + .cloned() // i'd rather avoid the double clone but i'm not sure how + .collect::>(); + + let persistence_id = update_state + .project_config + .as_ref() + .expect("project not loaded") + .project + .persistence_id; + // Group together so it looks nicer. ui.group(|ui| { + egui::TextEdit::singleline(&mut self.search_text) + .hint_text("Search 🔎") + .show(ui); + ui.separator(); + egui::ScrollArea::both() - .id_source(( - update_state - .project_config - .as_ref() - .expect("project not loaded") - .project - .persistence_id, - self.source, - )) + .id_source((persistence_id, self.source)) .auto_shrink([false, false]) // Show only visible rows. .show_rows( ui, row_height, - self.folder_children.len(), - |ui, row_range| { - for entry in &self.folder_children[row_range] { - // FIXME: Very hacky + filtered_childen.len() + 1, // +1 for (None) + |ui, mut row_range| { + // we really want to only show (None) if it's in range, we can collapse this but itd rely on short circuiting + #[allow(clippy::collapsible_if)] + if row_range.contains(&0) { + if ui + .selectable_value(&mut self.audio_file.name, None, "(None)") + .double_clicked() + { + self.play(update_state); + } + } + // subtract 1 to account for (None) + row_range.start = row_range.start.saturating_sub(1); + row_range.end = row_range.end.saturating_sub(1); + // FIXME display stripes somehow + for entry in &filtered_childen[row_range] { // Did the user double click a sound? if ui .selectable_value( - &mut self.selected_track, - entry.file_name().to_string(), + &mut self.audio_file.name, + Some(entry.file_name().into()), entry.file_name(), ) .double_clicked() { // Play it if they did. - let path = - format!("Audio/{}/{}", self.source, &self.selected_track); - let pitch = self.pitch; - let volume = self.volume; - let source = self.source; - - if let Err(e) = update_state.audio.play( - path, - update_state.filesystem, - volume, - pitch, - source, - ) { - luminol_core::error!( - update_state.toasts, - e.wrap_err("Error playing from audio file"), - ); - } + self.play(update_state); }; } }, diff --git a/crates/core/src/modal.rs b/crates/core/src/modal.rs index fcb7a531..902037d4 100644 --- a/crates/core/src/modal.rs +++ b/crates/core/src/modal.rs @@ -34,5 +34,6 @@ pub trait Modal: Sized { update_state: &'m mut crate::UpdateState<'_>, ) -> impl egui::Widget + 'm; // woah rpitit (so cool) + // FIXME is this used anywhere? fn reset(&mut self); } diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index d074e845..611de9d3 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -338,6 +338,7 @@ impl Modal { }); let mut is_faint = false; + // FIXME: CACHE THIS! let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); for Entry { path: entry ,invalid} in self.entries.iter_mut() { if matcher.fuzzy(entry.as_str(), &self.search_text, false).is_none() { diff --git a/crates/modals/src/sound_picker.rs b/crates/modals/src/sound_picker.rs index 87e7486d..57054d20 100644 --- a/crates/modals/src/sound_picker.rs +++ b/crates/modals/src/sound_picker.rs @@ -21,9 +21,22 @@ // 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 luminol_core::prelude::*; pub struct Modal { - _tab: luminol_components::SoundTab, + tab: luminol_components::SoundTab, + open: bool, +} + +impl Modal { + pub fn new( + filesystem: &impl FileSystem, + source: Source, + selected_track: luminol_data::rpg::AudioFile, + ) -> Self { + let tab = luminol_components::SoundTab::new(filesystem, source, selected_track); + Self { tab, open: false } + } } impl luminol_core::Modal for Modal { @@ -34,10 +47,56 @@ impl luminol_core::Modal for Modal { data: &'m mut Self::Data, update_state: &'m mut luminol_core::UpdateState<'_>, ) -> impl egui::Widget + 'm { - |ui: &mut egui::Ui| todo!() + |ui: &mut egui::Ui| { + let button_text = if let Some(track) = &self.tab.audio_file.name { + format!("Audio/{}/{}", self.tab.source, track) + } else { + "(None)".to_string() + }; + + let mut button_response = ui.button(button_text); + + if button_response.clicked() { + self.open = true; + } + if self.show_window(update_state, ui.ctx(), data) { + button_response.mark_changed() + } + + button_response + } } fn reset(&mut self) { - todo!() + self.open = false; + } +} + +impl Modal { + pub fn show_window( + &mut self, + update_state: &mut luminol_core::UpdateState<'_>, + ctx: &egui::Context, + data: &mut luminol_data::rpg::AudioFile, + ) -> bool { + let mut win_open = self.open; + let mut keep_open = true; + let mut needs_save = false; + + egui::Window::new("Graphic Picker") + .open(&mut win_open) + .show(ctx, |ui| { + self.tab.ui(ui, update_state); + ui.separator(); + + luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + }); + + if needs_save { + *data = self.tab.audio_file.clone(); + } + + self.open = win_open && keep_open; + needs_save } } diff --git a/crates/ui/src/windows/sound_test.rs b/crates/ui/src/windows/sound_test.rs index 341f4af1..fdba2ecc 100644 --- a/crates/ui/src/windows/sound_test.rs +++ b/crates/ui/src/windows/sound_test.rs @@ -38,7 +38,7 @@ impl Window { Self { // Create all sources. sources: luminol_audio::Source::iter() - .map(|s| luminol_components::SoundTab::new(filesystem, s)) + .map(|s| luminol_components::SoundTab::new(filesystem, s, Default::default())) .collect(), // By default, bgm is selected. selected_source: luminol_audio::Source::BGM, From fdb49259cda30f66cd2702d32e4df01199133406 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 08:22:43 -0700 Subject: [PATCH 34/67] cache sound tab results --- crates/components/src/sound_tab.rs | 41 +++++++++++++++++------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/crates/components/src/sound_tab.rs b/crates/components/src/sound_tab.rs index 00aab975..ebf96936 100644 --- a/crates/components/src/sound_tab.rs +++ b/crates/components/src/sound_tab.rs @@ -26,8 +26,10 @@ pub struct SoundTab { /// The source for this tab. pub source: luminol_audio::Source, pub audio_file: luminol_data::rpg::AudioFile, - folder_children: Vec, + search_text: String, + folder_children: Vec, + filtered_children: Vec, } impl SoundTab { @@ -42,8 +44,10 @@ impl SoundTab { Self { source, audio_file, - folder_children, + + filtered_children: folder_children.clone(), search_text: String::new(), + folder_children, } } @@ -125,19 +129,6 @@ impl SoundTab { // Get row height. let row_height = ui.text_style_height(&egui::TextStyle::Body); // i do not trust this - // FIXME: CACHE THIS! - let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); - let filtered_childen = self - .folder_children - .iter() - .filter(|entry| { - matcher - .fuzzy(entry.file_name(), &self.search_text, false) - .is_some() - }) - .cloned() // i'd rather avoid the double clone but i'm not sure how - .collect::>(); - let persistence_id = update_state .project_config .as_ref() @@ -147,9 +138,23 @@ impl SoundTab { // Group together so it looks nicer. ui.group(|ui| { - egui::TextEdit::singleline(&mut self.search_text) + let out = egui::TextEdit::singleline(&mut self.search_text) .hint_text("Search 🔎") .show(ui); + if out.response.changed() { + let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); + self.filtered_children = self + .folder_children + .iter() + .filter(|entry| { + matcher + .fuzzy(entry.file_name(), &self.search_text, false) + .is_some() + }) + .cloned() + .collect::>(); + println!("redoing search"); + } ui.separator(); egui::ScrollArea::both() @@ -159,7 +164,7 @@ impl SoundTab { .show_rows( ui, row_height, - filtered_childen.len() + 1, // +1 for (None) + self.filtered_children.len() + 1, // +1 for (None) |ui, mut row_range| { // we really want to only show (None) if it's in range, we can collapse this but itd rely on short circuiting #[allow(clippy::collapsible_if)] @@ -175,7 +180,7 @@ impl SoundTab { row_range.start = row_range.start.saturating_sub(1); row_range.end = row_range.end.saturating_sub(1); // FIXME display stripes somehow - for entry in &filtered_childen[row_range] { + for entry in &self.filtered_children[row_range] { // Did the user double click a sound? if ui .selectable_value( From 61b3be29c308499099aa41a7f6027aa6df88dfad Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 08:30:43 -0700 Subject: [PATCH 35/67] Cache filters --- crates/components/src/sound_tab.rs | 3 +- crates/modals/src/event_graphic_picker.rs | 131 ++++++++++------------ 2 files changed, 62 insertions(+), 72 deletions(-) diff --git a/crates/components/src/sound_tab.rs b/crates/components/src/sound_tab.rs index ebf96936..2337bb78 100644 --- a/crates/components/src/sound_tab.rs +++ b/crates/components/src/sound_tab.rs @@ -152,8 +152,7 @@ impl SoundTab { .is_some() }) .cloned() - .collect::>(); - println!("redoing search"); + .collect(); } ui.separator(); diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 611de9d3..de0f25ed 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -24,15 +24,16 @@ use color_eyre::eyre::Context; use egui::Widget; -use luminol_components::UiExt; use luminol_core::prelude::*; pub struct Modal { entries: Vec, + filtered_entries: Vec, + search_text: String, + open: bool, id_source: egui::Id, - search_text: String, selected: Selected, opacity: i32, hue: i32, @@ -45,7 +46,7 @@ pub struct Modal { button_sprite: Option, } -#[derive(PartialEq, PartialOrd, Eq, Ord)] +#[derive(PartialEq, PartialOrd, Eq, Ord, Clone)] struct Entry { path: camino::Utf8PathBuf, invalid: bool, @@ -117,11 +118,13 @@ impl Modal { .unwrap(); Self { + filtered_entries: entries.clone(), + search_text: String::new(), entries, + open: false, id_source, - search_text: String::new(), selected: Selected::None, opacity: graphic.opacity, hue: graphic.character_hue, @@ -297,81 +300,69 @@ impl Modal { .id(self.id_source.with("window")) .show(ctx, |ui| { egui::SidePanel::left(self.id_source.with("sidebar")).show_inside(ui, |ui| { - egui::TextEdit::singleline(&mut self.search_text) + let out = egui::TextEdit::singleline(&mut self.search_text) .hint_text("Search 🔎") .show(ui); + if out.response.changed() { + let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); + self.filtered_entries = self + .entries + .iter() + .filter(|entry| { + matcher + .fuzzy(entry.path.as_str(), &self.search_text, false) + .is_some() + }) + .cloned() + .collect(); + } + ui.separator(); // FIXME: Its better to use show_rows! + // FIXME show stripes! egui::ScrollArea::vertical().show(ui, |ui| { - ui.with_stripe(true, |ui| { - ui.horizontal(|ui| { - let res = ui.selectable_label(matches!(self.selected, Selected::None), "(None)"); - ui.add_space(ui.available_width()); - - if self.first_open && matches!(self.selected, Selected::None) { - res.scroll_to_me(Some(egui::Align::Center)); - } - - if res.clicked() && !matches!(self.selected, Selected::None) { - self.selected = Selected::None; - } - }); - }); - - ui.with_stripe(false, |ui| { - ui.horizontal(|ui| { - let checked = matches!(self.selected, Selected::Tile(_)); - let res = ui.selectable_label( - checked, - "(Tileset)", - ); - ui.add_space(ui.available_width()); - - if self.first_open && checked { - res.scroll_to_me(Some(egui::Align::Center)); - } + let res = ui.selectable_label(matches!(self.selected, Selected::None), "(None)"); + if self.first_open && matches!(self.selected, Selected::None) { + res.scroll_to_me(Some(egui::Align::Center)); + } + if res.clicked() && !matches!(self.selected, Selected::None) { + self.selected = Selected::None; + } - if res.clicked() && !checked { - self.selected = Selected::Tile(384); - } - }); - }); + let checked = matches!(self.selected, Selected::Tile(_)); + let res = ui.selectable_label( + checked, + "(Tileset)", + ); + if self.first_open && checked { + res.scroll_to_me(Some(egui::Align::Center)); + } + if res.clicked() && !checked { + self.selected = Selected::Tile(384); + } - let mut is_faint = false; - // FIXME: CACHE THIS! - let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); - for Entry { path: entry ,invalid} in self.entries.iter_mut() { - if matcher.fuzzy(entry.as_str(), &self.search_text, false).is_none() { - continue; + for Entry { path: entry ,invalid} in self.filtered_entries.iter_mut() { + let checked = + matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); + let mut text = egui::RichText::new(entry.as_str()); + if *invalid { + text = text.color(egui::Color32::LIGHT_RED); } - is_faint = !is_faint; - - ui.horizontal(|ui| { - ui.with_stripe(is_faint, |ui| { - let checked = - matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); - let mut text = egui::RichText::new(entry.as_str()); - if *invalid { - text = text.color(egui::Color32::LIGHT_RED); - } - let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); - ui.add_space(ui.available_width()); - if res.clicked() { - let sprite = match Self::load_preview_sprite(update_state, entry, self.hue, self.opacity) { - Ok(sprite) => sprite, - Err(e) => { - luminol_core::error!(update_state.toasts, e); - *invalid = true; - return; - } - }; - self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0, sprite }; + let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); + if res.clicked() { + let sprite = match Self::load_preview_sprite(update_state, entry, self.hue, self.opacity) { + Ok(sprite) => sprite, + Err(e) => { + luminol_core::error!(update_state.toasts, e); + *invalid = true; // FIXME update non-filtered entry too + return; } - if self.first_open && checked { - res.scroll_to_me(Some(egui::Align::Center)); - } - }); - }); + }; + self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0, sprite }; + } + if self.first_open && checked { + res.scroll_to_me(Some(egui::Align::Center)); + } } }); }); From 8898e21efafee3f41db5821c8d638999fa33036f Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 08:39:25 -0700 Subject: [PATCH 36/67] Use rows for graphic picker --- crates/modals/src/event_graphic_picker.rs | 94 ++++++++++++----------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index de0f25ed..b75cbc66 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -318,52 +318,60 @@ impl Modal { } ui.separator(); - // FIXME: Its better to use show_rows! + + // Get row height. + let row_height = ui.text_style_height(&egui::TextStyle::Body); // i do not trust this // FIXME show stripes! - egui::ScrollArea::vertical().show(ui, |ui| { - let res = ui.selectable_label(matches!(self.selected, Selected::None), "(None)"); - if self.first_open && matches!(self.selected, Selected::None) { - res.scroll_to_me(Some(egui::Align::Center)); - } - if res.clicked() && !matches!(self.selected, Selected::None) { - self.selected = Selected::None; - } + // FIXME scroll to selected on first open + egui::ScrollArea::vertical() + .auto_shrink([false, true]) + .show_rows( + ui, + row_height, + self.filtered_entries.len() + 2, + |ui, mut rows| { + if rows.contains(&0) { + let res = ui.selectable_label(matches!(self.selected, Selected::None), "(None)"); + if res.clicked() && !matches!(self.selected, Selected::None) { + self.selected = Selected::None; + } + } - let checked = matches!(self.selected, Selected::Tile(_)); - let res = ui.selectable_label( - checked, - "(Tileset)", - ); - if self.first_open && checked { - res.scroll_to_me(Some(egui::Align::Center)); - } - if res.clicked() && !checked { - self.selected = Selected::Tile(384); - } + if rows.contains(&1) { + let checked = matches!(self.selected, Selected::Tile(_)); + let res = ui.selectable_label( + checked, + "(Tileset)", + ); + if res.clicked() && !checked { + self.selected = Selected::Tile(384); + } + } - for Entry { path: entry ,invalid} in self.filtered_entries.iter_mut() { - let checked = - matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); - let mut text = egui::RichText::new(entry.as_str()); - if *invalid { - text = text.color(egui::Color32::LIGHT_RED); - } - let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); - if res.clicked() { - let sprite = match Self::load_preview_sprite(update_state, entry, self.hue, self.opacity) { - Ok(sprite) => sprite, - Err(e) => { - luminol_core::error!(update_state.toasts, e); - *invalid = true; // FIXME update non-filtered entry too - return; + // subtract 2 to account for (None) and (Tileset) + rows.start = rows.start.saturating_sub(2); + rows.end = rows.end.saturating_sub(2); + + for Entry { path: entry ,invalid} in self.filtered_entries[rows].iter_mut() { + let checked = + matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); + let mut text = egui::RichText::new(entry.as_str()); + if *invalid { + text = text.color(egui::Color32::LIGHT_RED); } - }; - self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0, sprite }; - } - if self.first_open && checked { - res.scroll_to_me(Some(egui::Align::Center)); - } - } + let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); + if res.clicked() { + let sprite = match Self::load_preview_sprite(update_state, entry, self.hue, self.opacity) { + Ok(sprite) => sprite, + Err(e) => { + luminol_core::error!(update_state.toasts, e); + *invalid = true; // FIXME update non-filtered entry too + return; + } + }; + self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0, sprite }; + } + } }); }); @@ -395,7 +403,7 @@ impl Modal { }); egui::CentralPanel::default().show_inside(ui, |ui| { - egui::ScrollArea::both().show_viewport(ui, |ui, viewport| { + egui::ScrollArea::both().auto_shrink([false,false]).show_viewport(ui, |ui, viewport| { match &mut self.selected { Selected::None => {} Selected::Graphic { direction, pattern, sprite, .. } => { From ceea9bf0d99a12c1d1d3479c1ec80320d54fcc32 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 09:09:48 -0700 Subject: [PATCH 37/67] Re-add stripes to graphic picker --- crates/components/src/sound_tab.rs | 59 +++++++------- crates/modals/src/event_graphic_picker.rs | 94 ++++++++++++----------- 2 files changed, 83 insertions(+), 70 deletions(-) diff --git a/crates/components/src/sound_tab.rs b/crates/components/src/sound_tab.rs index 2337bb78..db7e0098 100644 --- a/crates/components/src/sound_tab.rs +++ b/crates/components/src/sound_tab.rs @@ -22,6 +22,8 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. +use crate::UiExt; + pub struct SoundTab { /// The source for this tab. pub source: luminol_audio::Source, @@ -165,34 +167,39 @@ impl SoundTab { row_height, self.filtered_children.len() + 1, // +1 for (None) |ui, mut row_range| { - // we really want to only show (None) if it's in range, we can collapse this but itd rely on short circuiting - #[allow(clippy::collapsible_if)] - if row_range.contains(&0) { - if ui - .selectable_value(&mut self.audio_file.name, None, "(None)") - .double_clicked() - { - self.play(update_state); + ui.with_cross_justify(|ui| { + // we really want to only show (None) if it's in range, we can collapse this but itd rely on short circuiting + #[allow(clippy::collapsible_if)] + if row_range.contains(&0) { + if ui + .selectable_value(&mut self.audio_file.name, None, "(None)") + .double_clicked() + { + self.play(update_state); + } } - } - // subtract 1 to account for (None) - row_range.start = row_range.start.saturating_sub(1); - row_range.end = row_range.end.saturating_sub(1); - // FIXME display stripes somehow - for entry in &self.filtered_children[row_range] { - // Did the user double click a sound? - if ui - .selectable_value( - &mut self.audio_file.name, - Some(entry.file_name().into()), - entry.file_name(), - ) - .double_clicked() + // subtract 1 to account for (None) + row_range.start = row_range.start.saturating_sub(1); + row_range.end = row_range.end.saturating_sub(1); + for (i, entry) in + self.filtered_children[row_range.clone()].iter().enumerate() { - // Play it if they did. - self.play(update_state); - }; - } + let faint = (i + row_range.start) % 2 == 0; + let res = ui.with_stripe(faint, |ui| { + ui.selectable_value( + &mut self.audio_file.name, + Some(entry.file_name().into()), + entry.file_name(), + ) + }); + // need to move this out because the borrow checker isn't smart enough + // Did the user double click a sound? + if res.inner.double_clicked() { + // Play it if they did. + self.play(update_state); + }; + } + }); }, ); }); diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index b75cbc66..8c57206c 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -24,6 +24,7 @@ use color_eyre::eyre::Context; use egui::Widget; +use luminol_components::UiExt; use luminol_core::prelude::*; pub struct Modal { @@ -71,6 +72,7 @@ enum Selected { }, } +// FIXME DEAR GOD THE FORMATTING impl Modal { pub fn new( update_state: &UpdateState<'_>, @@ -321,57 +323,61 @@ impl Modal { // Get row height. let row_height = ui.text_style_height(&egui::TextStyle::Body); // i do not trust this - // FIXME show stripes! // FIXME scroll to selected on first open - egui::ScrollArea::vertical() - .auto_shrink([false, true]) - .show_rows( - ui, - row_height, - self.filtered_entries.len() + 2, - |ui, mut rows| { - if rows.contains(&0) { - let res = ui.selectable_label(matches!(self.selected, Selected::None), "(None)"); - if res.clicked() && !matches!(self.selected, Selected::None) { - self.selected = Selected::None; + ui.with_cross_justify(|ui| { + egui::ScrollArea::vertical() + .auto_shrink([false, true]) + .show_rows( + ui, + row_height, + self.filtered_entries.len() + 2, + |ui, mut rows| { + if rows.contains(&0) { + let res = ui.selectable_label(matches!(self.selected, Selected::None), "(None)"); + if res.clicked() && !matches!(self.selected, Selected::None) { + self.selected = Selected::None; + } } - } - if rows.contains(&1) { - let checked = matches!(self.selected, Selected::Tile(_)); - let res = ui.selectable_label( - checked, - "(Tileset)", - ); - if res.clicked() && !checked { - self.selected = Selected::Tile(384); + if rows.contains(&1) { + let checked = matches!(self.selected, Selected::Tile(_)); + ui.with_stripe(true, |ui| { + let res = ui.selectable_label(checked, "(Tileset)"); + if res.clicked() && !checked { + self.selected = Selected::Tile(384); + } + }); } - } - // subtract 2 to account for (None) and (Tileset) - rows.start = rows.start.saturating_sub(2); - rows.end = rows.end.saturating_sub(2); - - for Entry { path: entry ,invalid} in self.filtered_entries[rows].iter_mut() { - let checked = - matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); - let mut text = egui::RichText::new(entry.as_str()); - if *invalid { - text = text.color(egui::Color32::LIGHT_RED); - } - let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); - if res.clicked() { - let sprite = match Self::load_preview_sprite(update_state, entry, self.hue, self.opacity) { - Ok(sprite) => sprite, - Err(e) => { - luminol_core::error!(update_state.toasts, e); - *invalid = true; // FIXME update non-filtered entry too - return; + // subtract 2 to account for (None) and (Tileset) + rows.start = rows.start.saturating_sub(2); + rows.end = rows.end.saturating_sub(2); + + for (i, Entry { path: entry ,invalid}) in self.filtered_entries[rows.clone()].iter_mut().enumerate() { + let checked = + matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); + let mut text = egui::RichText::new(entry.as_str()); + if *invalid { + text = text.color(egui::Color32::LIGHT_RED); + } + let faint = (i + rows.start) % 2 == 1; + ui.with_stripe(faint, |ui| { + let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); + + if res.clicked() { + let sprite = match Self::load_preview_sprite(update_state, entry, self.hue, self.opacity) { + Ok(sprite) => sprite, + Err(e) => { + luminol_core::error!(update_state.toasts, e); + *invalid = true; // FIXME update non-filtered entry too + return; + } + }; + self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0, sprite }; } - }; - self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0, sprite }; + }); } - } + }); }); }); From 787c5cbbeb930c8c46c256596da4625b7ef274b4 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 09:12:34 -0700 Subject: [PATCH 38/67] Remove first open --- crates/modals/src/event_graphic_picker.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 8c57206c..ca786419 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -39,7 +39,6 @@ pub struct Modal { opacity: i32, hue: i32, blend_mode: rpg::BlendMode, - first_open: bool, tilepicker: Tilepicker, @@ -131,7 +130,6 @@ impl Modal { opacity: graphic.opacity, hue: graphic.character_hue, blend_mode: graphic.blend_type, - first_open: false, tilepicker, @@ -208,7 +206,6 @@ impl luminol_core::Modal for Modal { self.blend_mode = data.blend_type; self.hue = data.character_hue; self.opacity = data.opacity; - self.first_open = true; self.open = true; } @@ -506,8 +503,6 @@ impl Modal { }); }); - self.first_open = false; - if needs_save { match self.selected { Selected::None => { From 471b014c55908fe9de0a362142e265f005428aa3 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 10:14:30 -0700 Subject: [PATCH 39/67] Very destructive commit Not very happy with this because it clones things, which is a waste of memory --- crates/components/src/database_view.rs | 6 +- crates/data/src/rmxp/actor.rs | 2 +- crates/data/src/rmxp/armor.rs | 2 +- crates/data/src/rmxp/class.rs | 4 +- crates/data/src/rmxp/enemy.rs | 4 +- crates/data/src/rmxp/skill.rs | 2 +- crates/data/src/rmxp/state.rs | 2 +- crates/data/src/rmxp/weapon.rs | 2 +- crates/modals/src/sound_picker.rs | 50 ++-- crates/ui/src/windows/actors.rs | 112 +++++---- crates/ui/src/windows/armor.rs | 84 +++---- crates/ui/src/windows/classes.rs | 147 ++++++----- crates/ui/src/windows/enemies.rs | 322 ++++++++++++------------- crates/ui/src/windows/items.rs | 137 +++++------ crates/ui/src/windows/skills.rs | 106 ++++---- crates/ui/src/windows/states.rs | 97 ++++---- crates/ui/src/windows/weapons.rs | 87 ++++--- src/app/top_bar.rs | 16 +- 18 files changed, 568 insertions(+), 614 deletions(-) diff --git a/crates/components/src/database_view.rs b/crates/components/src/database_view.rs index 6ebad57c..c3832dc3 100644 --- a/crates/components/src/database_view.rs +++ b/crates/components/src/database_view.rs @@ -48,11 +48,11 @@ impl DatabaseView { pub fn show( &mut self, ui: &mut egui::Ui, - update_state: &luminol_core::UpdateState<'_>, + update_state: &mut luminol_core::UpdateState<'_>, label: impl Into, vec: &mut Vec, formatter: impl Fn(&T) -> String, - inner: impl FnOnce(&mut egui::Ui, &mut Vec, usize) -> R, + inner: impl FnOnce(&mut egui::Ui, &mut Vec, usize, &mut luminol_core::UpdateState<'_>) -> R, ) -> egui::InnerResponse> where T: luminol_data::rpg::DatabaseEntry, @@ -262,7 +262,7 @@ impl DatabaseView { DatabaseViewResponse { inner: (self.selected_id < vec.len()) - .then(|| inner(ui, vec, self.selected_id)), + .then(|| inner(ui, vec, self.selected_id, update_state)), modified, } }) diff --git a/crates/data/src/rmxp/actor.rs b/crates/data/src/rmxp/actor.rs index 256507a5..14dcd809 100644 --- a/crates/data/src/rmxp/actor.rs +++ b/crates/data/src/rmxp/actor.rs @@ -19,7 +19,7 @@ use crate::{ optional_path_serde, Path, Table2, }; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Actor")] pub struct Actor { diff --git a/crates/data/src/rmxp/armor.rs b/crates/data/src/rmxp/armor.rs index 35413abf..0fd745ce 100644 --- a/crates/data/src/rmxp/armor.rs +++ b/crates/data/src/rmxp/armor.rs @@ -19,7 +19,7 @@ use crate::{ optional_path_alox, optional_path_serde, Path, }; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Armor")] pub struct Armor { diff --git a/crates/data/src/rmxp/class.rs b/crates/data/src/rmxp/class.rs index 667d3859..4ae00d87 100644 --- a/crates/data/src/rmxp/class.rs +++ b/crates/data/src/rmxp/class.rs @@ -16,7 +16,7 @@ // along with Luminol. If not, see . pub use crate::{id_alox, id_serde, id_vec_alox, id_vec_serde, Table1}; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Class")] pub struct Class { @@ -36,7 +36,7 @@ pub struct Class { pub learnings: Vec, } -#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Class::Learning")] pub struct Learning { diff --git a/crates/data/src/rmxp/enemy.rs b/crates/data/src/rmxp/enemy.rs index 363cda58..e19269e8 100644 --- a/crates/data/src/rmxp/enemy.rs +++ b/crates/data/src/rmxp/enemy.rs @@ -19,7 +19,7 @@ pub use crate::{ optional_path_serde, Path, Table1, }; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Enemy")] pub struct Enemy { @@ -65,7 +65,7 @@ pub struct Enemy { pub treasure_prob: i32, } -#[derive(Debug, serde::Deserialize, serde::Serialize)] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Enemy::Action")] pub struct Action { diff --git a/crates/data/src/rmxp/skill.rs b/crates/data/src/rmxp/skill.rs index 820e7efd..32daf3aa 100644 --- a/crates/data/src/rmxp/skill.rs +++ b/crates/data/src/rmxp/skill.rs @@ -19,7 +19,7 @@ pub use crate::{ optional_path_alox, optional_path_serde, rpg::AudioFile, Path, }; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Skill")] pub struct Skill { diff --git a/crates/data/src/rmxp/state.rs b/crates/data/src/rmxp/state.rs index ffc4f442..d017ae41 100644 --- a/crates/data/src/rmxp/state.rs +++ b/crates/data/src/rmxp/state.rs @@ -16,7 +16,7 @@ // along with Luminol. If not, see . use crate::{id_alox, id_serde, id_vec_alox, id_vec_serde, optional_id_alox, optional_id_serde}; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::State")] pub struct State { diff --git a/crates/data/src/rmxp/weapon.rs b/crates/data/src/rmxp/weapon.rs index 52774f96..c2dffd31 100644 --- a/crates/data/src/rmxp/weapon.rs +++ b/crates/data/src/rmxp/weapon.rs @@ -19,7 +19,7 @@ pub use crate::{ optional_path_alox, optional_path_serde, rpg::AudioFile, Path, }; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Weapon")] pub struct Weapon { diff --git a/crates/modals/src/sound_picker.rs b/crates/modals/src/sound_picker.rs index 57054d20..217cd108 100644 --- a/crates/modals/src/sound_picker.rs +++ b/crates/modals/src/sound_picker.rs @@ -24,18 +24,23 @@ use luminol_core::prelude::*; pub struct Modal { - tab: luminol_components::SoundTab, - open: bool, + state: State, + id_source: egui::Id, + source: Source, +} + +enum State { + Closed, + Open { tab: luminol_components::SoundTab }, } impl Modal { - pub fn new( - filesystem: &impl FileSystem, - source: Source, - selected_track: luminol_data::rpg::AudioFile, - ) -> Self { - let tab = luminol_components::SoundTab::new(filesystem, source, selected_track); - Self { tab, open: false } + pub fn new(source: Source, id_source: impl Into) -> Self { + Self { + state: State::Closed, + id_source: id_source.into(), + source, + } } } @@ -48,8 +53,8 @@ impl luminol_core::Modal for Modal { update_state: &'m mut luminol_core::UpdateState<'_>, ) -> impl egui::Widget + 'm { |ui: &mut egui::Ui| { - let button_text = if let Some(track) = &self.tab.audio_file.name { - format!("Audio/{}/{}", self.tab.source, track) + let button_text = if let Some(track) = &data.name { + format!("Audio/{}/{}", self.source, track) } else { "(None)".to_string() }; @@ -57,7 +62,12 @@ impl luminol_core::Modal for Modal { let mut button_response = ui.button(button_text); if button_response.clicked() { - self.open = true; + let tab = luminol_components::SoundTab::new( + update_state.filesystem, + self.source, + data.clone(), + ); + self.state = State::Open { tab }; } if self.show_window(update_state, ui.ctx(), data) { button_response.mark_changed() @@ -68,7 +78,7 @@ impl luminol_core::Modal for Modal { } fn reset(&mut self) { - self.open = false; + self.state = State::Closed; } } @@ -79,24 +89,30 @@ impl Modal { ctx: &egui::Context, data: &mut luminol_data::rpg::AudioFile, ) -> bool { - let mut win_open = self.open; + let mut win_open = true; let mut keep_open = true; let mut needs_save = false; + let State::Open { tab } = &mut self.state else { + return false; + }; + egui::Window::new("Graphic Picker") .open(&mut win_open) .show(ctx, |ui| { - self.tab.ui(ui, update_state); + tab.ui(ui, update_state); ui.separator(); luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); }); if needs_save { - *data = self.tab.audio_file.clone(); + *data = tab.audio_file.clone(); } - self.open = win_open && keep_open; + if !(win_open && keep_open) { + self.state = State::Closed; + } needs_save } } diff --git a/crates/ui/src/windows/actors.rs b/crates/ui/src/windows/actors.rs index dbe35e03..bbd460da 100644 --- a/crates/ui/src/windows/actors.rs +++ b/crates/ui/src/windows/actors.rs @@ -27,8 +27,8 @@ use luminol_components::UiExt; use luminol_data::rpg::armor::Kind; -#[derive(Default)] pub struct Window { + actors: Vec, selected_actor_name: Option, previous_actor: Option, @@ -39,8 +39,16 @@ pub struct Window { } impl Window { - pub fn new() -> Self { - Default::default() + pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { + let actors = update_state.data.actors(); + Self { + actors: actors.data.clone(), + selected_actor_name: None, + previous_actor: None, + exp_view_is_total: false, + exp_view_is_depersisted: false, + view: luminol_components::DatabaseView::new(), + } } } @@ -222,13 +230,6 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut actors = update_state.data.actors(); - let mut classes = update_state.data.classes(); - let weapons = update_state.data.weapons(); - let armors = update_state.data.armors(); - - let mut modified = false; - self.selected_actor_name = None; let response = egui::Window::new(self.name()) @@ -240,40 +241,42 @@ impl luminol_core::Window for Window { ui, update_state, "Actors", - &mut actors.data, + &mut self.actors, |actor| format!("{:0>4}: {}", actor.id + 1, actor.name), - |ui, actors, id| { + |ui, actors, id, update_state| { let actor = &mut actors[id]; self.selected_actor_name = Some(actor.name.clone()); + let mut classes = update_state.data.classes(); + let weapons = update_state.data.weapons(); + let armors = update_state.data.armors(); + ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut actor.name) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut actor.name) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Class", - luminol_components::OptionalIdComboBox::new( - update_state, - (actor.id, "class"), - &mut actor.class_id, - 0..classes.data.len(), - |id| { - classes.data.get(id).map_or_else( - || "".into(), - |c| format!("{:0>4}: {}", id + 1, c.name), - ) - }, - ), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Class", + luminol_components::OptionalIdComboBox::new( + update_state, + (actor.id, "class"), + &mut actor.class_id, + 0..classes.data.len(), + |id| { + classes.data.get(id).map_or_else( + || "".into(), + |c| format!("{:0>4}: {}", id + 1, c.name), + ) + }, + ), + )) + .changed(); }); if let Some(class) = classes.data.get_mut(actor.class_id) { @@ -293,7 +296,7 @@ impl luminol_core::Window for Window { egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add( luminol_components::OptionalIdComboBox::new( update_state, @@ -325,7 +328,7 @@ impl luminol_core::Window for Window { ), ) .changed(); - modified |= columns[1] + columns[1] .checkbox(&mut actor.weapon_fix, "Fixed") .changed(); }); @@ -342,7 +345,7 @@ impl luminol_core::Window for Window { egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add( luminol_components::OptionalIdComboBox::new( update_state, @@ -381,7 +384,7 @@ impl luminol_core::Window for Window { ), ) .changed(); - modified |= columns[1] + columns[1] .checkbox(&mut actor.armor1_fix, "Fixed") .changed(); }); @@ -398,7 +401,7 @@ impl luminol_core::Window for Window { egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add( luminol_components::OptionalIdComboBox::new( update_state, @@ -437,7 +440,7 @@ impl luminol_core::Window for Window { ), ) .changed(); - modified |= columns[1] + columns[1] .checkbox(&mut actor.armor2_fix, "Fixed") .changed(); }); @@ -454,7 +457,7 @@ impl luminol_core::Window for Window { egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add( luminol_components::OptionalIdComboBox::new( update_state, @@ -493,7 +496,7 @@ impl luminol_core::Window for Window { ), ) .changed(); - modified |= columns[1] + columns[1] .checkbox(&mut actor.armor3_fix, "Fixed") .changed(); }); @@ -510,7 +513,7 @@ impl luminol_core::Window for Window { egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add( luminol_components::OptionalIdComboBox::new( update_state, @@ -549,7 +552,7 @@ impl luminol_core::Window for Window { ), ) .changed(); - modified |= columns[1] + columns[1] .checkbox(&mut actor.armor4_fix, "Fixed") .changed(); }); @@ -561,14 +564,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Initial Level", egui::Slider::new(&mut actor.initial_level, 1..=99), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Final Level", egui::Slider::new( @@ -610,14 +613,14 @@ impl luminol_core::Window for Window { }); ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "EXP Curve Basis", egui::Slider::new(&mut actor.exp_basis, 10..=50), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "EXP Curve Inflation", egui::Slider::new(&mut actor.exp_inflation, 10..=50), @@ -721,13 +724,6 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { - modified = true; - } - - if modified { - update_state.modified.set(true); - actors.modified = true; - } + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} } } diff --git a/crates/ui/src/windows/armor.rs b/crates/ui/src/windows/armor.rs index e1d0eb50..61ef5498 100644 --- a/crates/ui/src/windows/armor.rs +++ b/crates/ui/src/windows/armor.rs @@ -24,8 +24,8 @@ use luminol_components::UiExt; -#[derive(Default)] pub struct Window { + armors: Vec, selected_armor_name: Option, previous_armor: Option, @@ -33,8 +33,14 @@ pub struct Window { } impl Window { - pub fn new() -> Self { - Default::default() + pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { + let armors = update_state.data.armors(); + Self { + armors: armors.data.clone(), + selected_armor_name: None, + previous_armor: None, + view: luminol_components::DatabaseView::new(), + } } } @@ -61,12 +67,6 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut armors = update_state.data.armors(); - let system = update_state.data.system(); - let states = update_state.data.states(); - - let mut modified = false; - self.selected_armor_name = None; let response = egui::Window::new(self.name()) @@ -78,33 +78,34 @@ impl luminol_core::Window for Window { ui, update_state, "Armor", - &mut armors.data, + &mut self.armors, |armor| format!("{:0>4}: {}", armor.id + 1, armor.name), - |ui, armors, id| { + |ui, armors, id, update_state| { let armor = &mut armors[id]; self.selected_armor_name = Some(armor.name.clone()); + let system = update_state.data.system(); + let states = update_state.data.states(); + ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut armor.name) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut armor.name) + .desired_width(f32::INFINITY), + )) + .changed(); - modified |= ui - .add(luminol_components::Field::new( - "Description", - egui::TextEdit::multiline(&mut armor.description) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Description", + egui::TextEdit::multiline(&mut armor.description) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Kind", luminol_components::EnumComboBox::new( @@ -114,7 +115,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Auto State", luminol_components::OptionalIdComboBox::new( @@ -136,7 +137,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(4, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Price", egui::DragValue::new(&mut armor.price) @@ -144,7 +145,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "EVA", egui::DragValue::new(&mut armor.eva) @@ -152,7 +153,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[2] + columns[2] .add(luminol_components::Field::new( "PDEF", egui::DragValue::new(&mut armor.pdef) @@ -160,7 +161,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[3] + columns[3] .add(luminol_components::Field::new( "MDEF", egui::DragValue::new(&mut armor.mdef) @@ -172,28 +173,28 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(4, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "STR+", egui::DragValue::new(&mut armor.str_plus), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "DEX+", egui::DragValue::new(&mut armor.dex_plus), )) .changed(); - modified |= columns[2] + columns[2] .add(luminol_components::Field::new( "AGI+", egui::DragValue::new(&mut armor.agi_plus), )) .changed(); - modified |= columns[3] + columns[3] .add(luminol_components::Field::new( "INT+", egui::DragValue::new(&mut armor.int_plus), @@ -219,7 +220,7 @@ impl luminol_core::Window for Window { if self.previous_armor != Some(armor.id) { selection.clear_search(); } - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Element Defense", selection, @@ -241,7 +242,7 @@ impl luminol_core::Window for Window { if self.previous_armor != Some(armor.id) { selection.clear_search(); } - modified |= columns[1] + columns[1] .add(luminol_components::Field::new("States", selection)) .changed(); }); @@ -252,13 +253,6 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { - modified = true; - } - - if modified { - update_state.modified.set(true); - armors.modified = true; - } + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} } } diff --git a/crates/ui/src/windows/classes.rs b/crates/ui/src/windows/classes.rs index 835b575a..da70b4b0 100644 --- a/crates/ui/src/windows/classes.rs +++ b/crates/ui/src/windows/classes.rs @@ -24,8 +24,8 @@ use luminol_components::UiExt; -#[derive(Default)] pub struct Window { + classes: Vec, selected_class_name: Option, previous_class: Option, @@ -34,8 +34,16 @@ pub struct Window { } impl Window { - pub fn new() -> Self { - Default::default() + pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { + let classes = update_state.data.classes(); + + Self { + classes: classes.data.clone(), + selected_class_name: None, + previous_class: None, + collapsing_view: luminol_components::CollapsingView::new(), + view: luminol_components::DatabaseView::new(), + } } fn show_learning_header( @@ -58,19 +66,18 @@ impl Window { learning: (usize, &mut luminol_data::rpg::class::Learning), ) -> egui::Response { let (learning_index, learning) = learning; - let mut modified = false; - let mut response = egui::Frame::none() + egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Level", egui::Slider::new(&mut learning.level, 1..=99), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Skill", luminol_components::OptionalIdComboBox::new( @@ -89,12 +96,7 @@ impl Window { .changed(); }); }) - .response; - - if modified { - response.mark_changed(); - } - response + .response } } @@ -121,15 +123,6 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut classes = update_state.data.classes(); - let system = update_state.data.system(); - let states = update_state.data.states(); - let skills = update_state.data.skills(); - let weapons = update_state.data.weapons(); - let armors = update_state.data.armors(); - - let mut modified = false; - self.selected_class_name = None; let response = egui::Window::new(self.name()) @@ -141,62 +134,65 @@ impl luminol_core::Window for Window { ui, update_state, "Classes", - &mut classes.data, + &mut self.classes, |class| format!("{:0>3}: {}", class.id + 1, class.name), - |ui, classes, id| { + |ui, classes, id, update_state| { let class = &mut classes[id]; self.selected_class_name = Some(class.name.clone()); + let system = update_state.data.system(); + let states = update_state.data.states(); + let skills = update_state.data.skills(); + let weapons = update_state.data.weapons(); + let armors = update_state.data.armors(); + ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut class.name) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut class.name) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Position", - luminol_components::EnumComboBox::new( - (class.id, "position"), - &mut class.position, - ), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Position", + luminol_components::EnumComboBox::new( + (class.id, "position"), + &mut class.position, + ), + )) + .changed(); }); ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Skills", - |ui: &mut egui::Ui| { - if self.previous_class != Some(class.id) { - self.collapsing_view.clear_animations(); - } - self.collapsing_view.show( - ui, - class.id, - &mut class.learnings, - |ui, _i, learning| { - Self::show_learning_header(ui, &skills, learning) - }, - |ui, i, learning| { - Self::show_learning_body( - ui, - update_state, - &skills, - class.id, - (i, learning), - ) - }, - ) - }, - )) - .changed(); + ui.add(luminol_components::Field::new( + "Skills", + |ui: &mut egui::Ui| { + if self.previous_class != Some(class.id) { + self.collapsing_view.clear_animations(); + } + self.collapsing_view.show( + ui, + class.id, + &mut class.learnings, + |ui, _i, learning| { + Self::show_learning_header(ui, &skills, learning) + }, + |ui, i, learning| { + Self::show_learning_body( + ui, + update_state, + &skills, + class.id, + (i, learning), + ) + }, + ) + }, + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { @@ -216,7 +212,7 @@ impl luminol_core::Window for Window { if self.previous_class != Some(class.id) { selection.clear_search(); } - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Equippable Weapons", selection, @@ -238,7 +234,7 @@ impl luminol_core::Window for Window { if self.previous_class != Some(class.id) { selection.clear_search(); } - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Equippable Armor", selection, @@ -266,7 +262,7 @@ impl luminol_core::Window for Window { if self.previous_class != Some(class.id) { selection.clear_search(); } - modified |= columns[0] + columns[0] .add(luminol_components::Field::new("Elements", selection)) .changed(); @@ -287,7 +283,7 @@ impl luminol_core::Window for Window { if self.previous_class != Some(class.id) { selection.clear_search(); } - modified |= columns[1] + columns[1] .add(luminol_components::Field::new("States", selection)) .changed(); }); @@ -298,13 +294,6 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { - modified = true; - } - - if modified { - update_state.modified.set(true); - classes.modified = true; - } + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} } } diff --git a/crates/ui/src/windows/enemies.rs b/crates/ui/src/windows/enemies.rs index 08e0c9e4..836a754f 100644 --- a/crates/ui/src/windows/enemies.rs +++ b/crates/ui/src/windows/enemies.rs @@ -35,8 +35,8 @@ pub enum TreasureType { Armor, } -#[derive(Default)] pub struct Window { + enemies: Vec, selected_enemy_name: Option, previous_enemy: Option, @@ -45,8 +45,15 @@ pub struct Window { } impl Window { - pub fn new() -> Self { - Default::default() + pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { + let enimies = update_state.data.enemies(); + Self { + enemies: enimies.data.clone(), + selected_enemy_name: None, + previous_enemy: None, + collapsing_view: luminol_components::CollapsingView::new(), + view: luminol_components::DatabaseView::new(), + } } fn show_action_header( @@ -109,12 +116,11 @@ impl Window { action: (usize, &mut luminol_data::rpg::enemy::Action), ) -> egui::Response { let (action_index, action) = action; - let mut modified = false; - let mut response = egui::Frame::none() + egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Turn Offset", egui::DragValue::new(&mut action.condition_turn_a) @@ -122,7 +128,7 @@ impl Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Turn Interval", egui::DragValue::new(&mut action.condition_turn_b) @@ -132,14 +138,14 @@ impl Window { }); ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Max HP %", egui::Slider::new(&mut action.condition_hp, 0..=100).suffix("%"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Min Level", egui::Slider::new(&mut action.condition_level, 1..=99), @@ -147,26 +153,25 @@ impl Window { .changed(); }); - modified |= ui - .add(luminol_components::Field::new( - "Switch", - luminol_components::OptionalIdComboBox::new( - update_state, - (enemy_id, action_index, "condition_switch_id"), - &mut action.condition_switch_id, - 0..system.switches.len(), - |id| { - system - .switches - .get(id) - .map_or_else(|| "".into(), |s| format!("{:0>4}: {}", id + 1, s)) - }, - ), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Switch", + luminol_components::OptionalIdComboBox::new( + update_state, + (enemy_id, action_index, "condition_switch_id"), + &mut action.condition_switch_id, + 0..system.switches.len(), + |id| { + system + .switches + .get(id) + .map_or_else(|| "".into(), |s| format!("{:0>4}: {}", id + 1, s)) + }, + ), + )) + .changed(); ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Kind", luminol_components::EnumComboBox::new( @@ -178,7 +183,7 @@ impl Window { match action.kind { luminol_data::rpg::enemy::Kind::Basic => { - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Basic Type", luminol_components::EnumComboBox::new( @@ -189,7 +194,7 @@ impl Window { .changed(); } luminol_data::rpg::enemy::Kind::Skill => { - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Skill", luminol_components::OptionalIdComboBox::new( @@ -210,19 +215,13 @@ impl Window { } }); - modified |= ui - .add(luminol_components::Field::new( - "Rating", - egui::Slider::new(&mut action.rating, 1..=10), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Rating", + egui::Slider::new(&mut action.rating, 1..=10), + )) + .changed(); }) - .response; - - if modified { - response.mark_changed(); - } - response + .response } } @@ -249,17 +248,6 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut enemies = update_state.data.enemies(); - let animations = update_state.data.animations(); - let system = update_state.data.system(); - let states = update_state.data.states(); - let skills = update_state.data.skills(); - let items = update_state.data.items(); - let weapons = update_state.data.weapons(); - let armors = update_state.data.armors(); - - let mut modified = false; - self.selected_enemy_name = None; let response = egui::Window::new(self.name()) @@ -271,25 +259,32 @@ impl luminol_core::Window for Window { ui, update_state, "Enemies", - &mut enemies.data, + &mut self.enemies, |enemy| format!("{:0>4}: {}", enemy.id + 1, enemy.name), - |ui, enemies, id| { + |ui, enemies, id, update_state| { let enemy = &mut enemies[id]; self.selected_enemy_name = Some(enemy.name.clone()); + let animations = update_state.data.animations(); + let system = update_state.data.system(); + let states = update_state.data.states(); + let skills = update_state.data.skills(); + let items = update_state.data.items(); + let weapons = update_state.data.weapons(); + let armors = update_state.data.armors(); + ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut enemy.name) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut enemy.name) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Attacker Animation", luminol_components::OptionalIdComboBox::new( @@ -307,7 +302,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Target Animation", luminol_components::OptionalIdComboBox::new( @@ -329,7 +324,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(4, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "EXP", egui::DragValue::new(&mut enemy.exp) @@ -337,7 +332,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Gold", egui::DragValue::new(&mut enemy.gold) @@ -345,7 +340,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[2] + columns[2] .add(luminol_components::Field::new( "Max HP", egui::DragValue::new(&mut enemy.maxhp) @@ -353,7 +348,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[3] + columns[3] .add(luminol_components::Field::new( "Max SP", egui::DragValue::new(&mut enemy.maxsp) @@ -365,7 +360,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(4, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "STR", egui::DragValue::new(&mut enemy.str) @@ -373,7 +368,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "DEX", egui::DragValue::new(&mut enemy.dex) @@ -381,7 +376,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[2] + columns[2] .add(luminol_components::Field::new( "AGI", egui::DragValue::new(&mut enemy.agi) @@ -389,7 +384,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[3] + columns[3] .add(luminol_components::Field::new( "INT", egui::DragValue::new(&mut enemy.int) @@ -401,7 +396,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(4, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "ATK", egui::DragValue::new(&mut enemy.atk) @@ -409,7 +404,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "EVA", egui::DragValue::new(&mut enemy.eva) @@ -417,7 +412,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[2] + columns[2] .add(luminol_components::Field::new( "PDEF", egui::DragValue::new(&mut enemy.pdef) @@ -425,7 +420,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[3] + columns[3] .add(luminol_components::Field::new( "MDEF", egui::DragValue::new(&mut enemy.mdef) @@ -447,7 +442,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Treasure Type", luminol_components::EnumComboBox::new( @@ -457,7 +452,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Treasure Probability", egui::Slider::new(&mut enemy.treasure_prob, 0..=100) @@ -479,24 +474,23 @@ impl luminol_core::Window for Window { if enemy.item_id.is_none() { enemy.item_id = Some(0); } - modified |= ui - .add(luminol_components::Field::new( - "Treasure", - luminol_components::OptionalIdComboBox::new( - update_state, - (enemy.id, "item_id"), - &mut enemy.item_id, - 0..items.data.len(), - |id| { - items.data.get(id).map_or_else( - || "".into(), - |i| format!("{:0>4}: {}", id + 1, i.name), - ) - }, - ) - .allow_none(false), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Treasure", + luminol_components::OptionalIdComboBox::new( + update_state, + (enemy.id, "item_id"), + &mut enemy.item_id, + 0..items.data.len(), + |id| { + items.data.get(id).map_or_else( + || "".into(), + |i| format!("{:0>4}: {}", id + 1, i.name), + ) + }, + ) + .allow_none(false), + )) + .changed(); } TreasureType::Weapon => { @@ -505,24 +499,23 @@ impl luminol_core::Window for Window { if enemy.weapon_id.is_none() { enemy.weapon_id = Some(0); } - modified |= ui - .add(luminol_components::Field::new( - "Treasure", - luminol_components::OptionalIdComboBox::new( - update_state, - (enemy.id, "weapon_id"), - &mut enemy.weapon_id, - 0..weapons.data.len(), - |id| { - weapons.data.get(id).map_or_else( - || "".into(), - |w| format!("{:0>4}: {}", id + 1, w.name), - ) - }, - ) - .allow_none(false), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Treasure", + luminol_components::OptionalIdComboBox::new( + update_state, + (enemy.id, "weapon_id"), + &mut enemy.weapon_id, + 0..weapons.data.len(), + |id| { + weapons.data.get(id).map_or_else( + || "".into(), + |w| format!("{:0>4}: {}", id + 1, w.name), + ) + }, + ) + .allow_none(false), + )) + .changed(); } TreasureType::Armor => { @@ -531,57 +524,55 @@ impl luminol_core::Window for Window { if enemy.armor_id.is_none() { enemy.armor_id = Some(0); } - modified |= ui - .add(luminol_components::Field::new( - "Treasure", - luminol_components::OptionalIdComboBox::new( - update_state, - (enemy.id, "armor_id"), - &mut enemy.armor_id, - 0..armors.data.len(), - |id| { - armors.data.get(id).map_or_else( - || "".into(), - |a| format!("{:0>4}: {}", id + 1, a.name), - ) - }, - ) - .allow_none(false), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Treasure", + luminol_components::OptionalIdComboBox::new( + update_state, + (enemy.id, "armor_id"), + &mut enemy.armor_id, + 0..armors.data.len(), + |id| { + armors.data.get(id).map_or_else( + || "".into(), + |a| format!("{:0>4}: {}", id + 1, a.name), + ) + }, + ) + .allow_none(false), + )) + .changed(); } }; }); ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Actions", - |ui: &mut egui::Ui| { - if self.previous_enemy != Some(enemy.id) { - self.collapsing_view.clear_animations(); - } - self.collapsing_view.show( - ui, - enemy.id, - &mut enemy.actions, - |ui, _i, action| { - Self::show_action_header(ui, &skills, action) - }, - |ui, i, action| { - Self::show_action_body( - ui, - update_state, - &system, - &skills, - enemy.id, - (i, action), - ) - }, - ) - }, - )) - .changed(); + ui.add(luminol_components::Field::new( + "Actions", + |ui: &mut egui::Ui| { + if self.previous_enemy != Some(enemy.id) { + self.collapsing_view.clear_animations(); + } + self.collapsing_view.show( + ui, + enemy.id, + &mut enemy.actions, + |ui, _i, action| { + Self::show_action_header(ui, &skills, action) + }, + |ui, i, action| { + Self::show_action_body( + ui, + update_state, + &system, + &skills, + enemy.id, + (i, action), + ) + }, + ) + }, + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { @@ -603,7 +594,7 @@ impl luminol_core::Window for Window { if self.previous_enemy != Some(enemy.id) { selection.clear_search(); } - modified |= columns[0] + columns[0] .add(luminol_components::Field::new("Elements", selection)) .changed(); @@ -624,7 +615,7 @@ impl luminol_core::Window for Window { if self.previous_enemy != Some(enemy.id) { selection.clear_search(); } - modified |= columns[1] + columns[1] .add(luminol_components::Field::new("States", selection)) .changed(); }); @@ -635,13 +626,6 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { - modified = true; - } - - if modified { - update_state.modified.set(true); - enemies.modified = true; - } + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} } } diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index a1c3981b..9a6d21c6 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -23,18 +23,15 @@ // Program grant you additional permission to convey the resulting work. use luminol_components::UiExt; +use luminol_core::Modal; /// Database - Items management window. -#[derive(Default)] pub struct Window { - // ? Items ? + items: Vec, selected_item_name: Option, - // ? Icon Graphic Picker ? - _icon_picker: Option, - // ? Menu Sound Effect Picker ? - _menu_se_picker: Option, + menu_se_picker: luminol_modals::sound_picker::Modal, previous_item: Option, @@ -42,8 +39,18 @@ pub struct Window { } impl Window { - pub fn new() -> Self { - Default::default() + pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { + let items = update_state.data.items(); + Self { + items: items.data.clone(), + selected_item_name: None, + menu_se_picker: luminol_modals::sound_picker::Modal::new( + luminol_audio::Source::SE, + "item_editor_sound_picker", + ), + previous_item: None, + view: luminol_components::DatabaseView::new(), + } } } @@ -70,17 +77,9 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut items = update_state.data.items(); - let animations = update_state.data.animations(); - let common_events = update_state.data.common_events(); - let system = update_state.data.system(); - let states = update_state.data.states(); - - let mut modified = false; - self.selected_item_name = None; - let response = egui::Window::new(self.name()) + egui::Window::new(self.name()) .id(self.id()) .default_width(500.) .open(open) @@ -89,33 +88,31 @@ impl luminol_core::Window for Window { ui, update_state, "Items", - &mut items.data, + &mut self.items, |item| format!("{:0>4}: {}", item.id + 1, item.name), - |ui, items, id| { + |ui, items, id, update_state| { let item = &mut items[id]; self.selected_item_name = Some(item.name.clone()); ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut item.name) - .desired_width(f32::INFINITY), - )) - .changed(); - - modified |= ui - .add(luminol_components::Field::new( - "Description", - egui::TextEdit::multiline(&mut item.description) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut item.name) + .desired_width(f32::INFINITY), + )) + .changed(); + + ui.add(luminol_components::Field::new( + "Description", + egui::TextEdit::multiline(&mut item.description) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Scope", luminol_components::EnumComboBox::new( @@ -125,7 +122,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Occasion", luminol_components::EnumComboBox::new( @@ -138,8 +135,9 @@ impl luminol_core::Window for Window { }); ui.with_padded_stripe(false, |ui| { + let animations = update_state.data.animations(); ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "User Animation", luminol_components::OptionalIdComboBox::new( @@ -157,7 +155,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Target Animation", luminol_components::OptionalIdComboBox::new( @@ -179,14 +177,15 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Menu Use SE", - egui::Label::new("TODO"), + self.menu_se_picker.button(&mut item.menu_se, update_state), )) .changed(); - modified |= columns[1] + let common_events = update_state.data.common_events(); + columns[1] .add(luminol_components::Field::new( "Common Event", luminol_components::OptionalIdComboBox::new( @@ -208,7 +207,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Price", egui::DragValue::new(&mut item.price) @@ -216,7 +215,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Consumable", egui::Checkbox::without_text(&mut item.consumable), @@ -227,18 +226,17 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { if item.parameter_type.is_none() { - modified |= ui - .add(luminol_components::Field::new( - "Parameter", - luminol_components::EnumComboBox::new( - "parameter_type", - &mut item.parameter_type, - ), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Parameter", + luminol_components::EnumComboBox::new( + "parameter_type", + &mut item.parameter_type, + ), + )) + .changed(); } else { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Parameter", luminol_components::EnumComboBox::new( @@ -248,7 +246,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Parameter Increment", egui::DragValue::new(&mut item.parameter_points) @@ -261,7 +259,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Recover HP %", egui::Slider::new(&mut item.recover_hp_rate, 0..=100) @@ -269,7 +267,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Recover HP Points", egui::DragValue::new(&mut item.recover_hp) @@ -281,7 +279,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Recover SP %", egui::Slider::new(&mut item.recover_sp_rate, 0..=100) @@ -289,7 +287,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Recover SP Points", egui::DragValue::new(&mut item.recover_sp) @@ -301,14 +299,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Hit Rate", egui::Slider::new(&mut item.hit, 0..=100).suffix("%"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Variance", egui::Slider::new(&mut item.variance, 0..=100).suffix("%"), @@ -319,14 +317,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "PDEF-F", egui::Slider::new(&mut item.pdef_f, 0..=100).suffix("%"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "MDEF-F", egui::Slider::new(&mut item.mdef_f, 0..=100).suffix("%"), @@ -336,6 +334,8 @@ impl luminol_core::Window for Window { }); ui.with_padded_stripe(false, |ui| { + let system = update_state.data.system(); + let states = update_state.data.states(); ui.columns(2, |columns| { let mut selection = luminol_components::IdVecSelection::new( update_state, @@ -352,7 +352,7 @@ impl luminol_core::Window for Window { if self.previous_item != Some(item.id) { selection.clear_search(); } - modified |= columns[0] + columns[0] .add(luminol_components::Field::new("Elements", selection)) .changed(); @@ -373,7 +373,7 @@ impl luminol_core::Window for Window { if self.previous_item != Some(item.id) { selection.clear_search(); } - modified |= columns[1] + columns[1] .add(luminol_components::Field::new("State Change", selection)) .changed(); }); @@ -383,14 +383,5 @@ impl luminol_core::Window for Window { }, ) }); - - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { - modified = true; - } - - if modified { - update_state.modified.set(true); - items.modified = true; - } } } diff --git a/crates/ui/src/windows/skills.rs b/crates/ui/src/windows/skills.rs index 0d194751..58a8694a 100644 --- a/crates/ui/src/windows/skills.rs +++ b/crates/ui/src/windows/skills.rs @@ -24,8 +24,8 @@ use luminol_components::UiExt; -#[derive(Default)] pub struct Window { + skills: Vec, selected_skill_name: Option, previous_skill: Option, @@ -33,8 +33,14 @@ pub struct Window { } impl Window { - pub fn new() -> Self { - Default::default() + pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { + let skills = update_state.data.skills(); + Self { + skills: skills.data.clone(), + selected_skill_name: None, + previous_skill: None, + view: luminol_components::DatabaseView::new(), + } } } @@ -61,14 +67,6 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut skills = update_state.data.skills(); - let animations = update_state.data.animations(); - let common_events = update_state.data.common_events(); - let system = update_state.data.system(); - let states = update_state.data.states(); - - let mut modified = false; - self.selected_skill_name = None; let response = egui::Window::new(self.name()) @@ -80,33 +78,36 @@ impl luminol_core::Window for Window { ui, update_state, "Skills", - &mut skills.data, + &mut self.skills, |skill| format!("{:0>4}: {}", skill.id + 1, skill.name), - |ui, skills, id| { + |ui, skills, id, update_state| { let skill = &mut skills[id]; self.selected_skill_name = Some(skill.name.clone()); + let animations = update_state.data.animations(); + let common_events = update_state.data.common_events(); + let system = update_state.data.system(); + let states = update_state.data.states(); + ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut skill.name) - .desired_width(f32::INFINITY), - )) - .changed(); - - modified |= ui - .add(luminol_components::Field::new( - "Description", - egui::TextEdit::multiline(&mut skill.description) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut skill.name) + .desired_width(f32::INFINITY), + )) + .changed(); + + ui.add(luminol_components::Field::new( + "Description", + egui::TextEdit::multiline(&mut skill.description) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Scope", luminol_components::EnumComboBox::new( @@ -116,7 +117,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Occasion", luminol_components::EnumComboBox::new( @@ -130,7 +131,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "User Animation", luminol_components::OptionalIdComboBox::new( @@ -148,7 +149,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Target Animation", luminol_components::OptionalIdComboBox::new( @@ -170,14 +171,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Menu Use SE", egui::Label::new("TODO"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Common Event", luminol_components::OptionalIdComboBox::new( @@ -199,7 +200,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "SP Cost", egui::DragValue::new(&mut skill.sp_cost) @@ -207,7 +208,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Power", egui::DragValue::new(&mut skill.power), @@ -218,14 +219,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "ATK-F", egui::Slider::new(&mut skill.atk_f, 0..=200).suffix("%"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "EVA-F", egui::Slider::new(&mut skill.eva_f, 0..=100).suffix("%"), @@ -236,14 +237,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "STR-F", egui::Slider::new(&mut skill.str_f, 0..=100).suffix("%"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "DEX-F", egui::Slider::new(&mut skill.dex_f, 0..=100).suffix("%"), @@ -254,14 +255,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "AGI-F", egui::Slider::new(&mut skill.agi_f, 0..=100).suffix("%"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "INT-F", egui::Slider::new(&mut skill.int_f, 0..=100).suffix("%"), @@ -272,14 +273,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Hit Rate", egui::Slider::new(&mut skill.hit, 0..=100).suffix("%"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Variance", egui::Slider::new(&mut skill.variance, 0..=100).suffix("%"), @@ -290,14 +291,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "PDEF-F", egui::Slider::new(&mut skill.pdef_f, 0..=100).suffix("%"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "MDEF-F", egui::Slider::new(&mut skill.mdef_f, 0..=100).suffix("%"), @@ -323,7 +324,7 @@ impl luminol_core::Window for Window { if self.previous_skill != Some(skill.id) { selection.clear_search(); } - modified |= columns[0] + columns[0] .add(luminol_components::Field::new("Elements", selection)) .changed(); @@ -344,7 +345,7 @@ impl luminol_core::Window for Window { if self.previous_skill != Some(skill.id) { selection.clear_search(); } - modified |= columns[1] + columns[1] .add(luminol_components::Field::new("State Change", selection)) .changed(); }); @@ -355,13 +356,6 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { - modified = true; - } - - if modified { - update_state.modified.set(true); - skills.modified = true; - } + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} } } diff --git a/crates/ui/src/windows/states.rs b/crates/ui/src/windows/states.rs index 08862cdd..d578cb83 100644 --- a/crates/ui/src/windows/states.rs +++ b/crates/ui/src/windows/states.rs @@ -24,8 +24,8 @@ use luminol_components::UiExt; -#[derive(Default)] pub struct Window { + states: Vec, selected_state_name: Option, previous_state: Option, @@ -33,8 +33,14 @@ pub struct Window { } impl Window { - pub fn new() -> Self { - Default::default() + pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { + let states = update_state.data.states(); + Self { + states: states.data.clone(), + selected_state_name: None, + previous_state: None, + view: luminol_components::DatabaseView::new(), + } } } @@ -61,12 +67,6 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut states = update_state.data.states(); - let animations = update_state.data.animations(); - let system = update_state.data.system(); - - let mut modified = false; - self.selected_state_name = None; let response = egui::Window::new(self.name()) @@ -78,25 +78,27 @@ impl luminol_core::Window for Window { ui, update_state, "States", - &mut states.data, + &mut self.states, |state| format!("{:0>4}: {}", state.id + 1, state.name), - |ui, states, id| { + |ui, states, id, update_state| { let state = &mut states[id]; self.selected_state_name = Some(state.name.clone()); + let animations = update_state.data.animations(); + let system = update_state.data.system(); + ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut state.name) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut state.name) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Animation", luminol_components::OptionalIdComboBox::new( @@ -114,7 +116,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Restriction", luminol_components::EnumComboBox::new( @@ -128,14 +130,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Nonresistance", egui::Checkbox::without_text(&mut state.nonresistance), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Count as 0 HP", egui::Checkbox::without_text(&mut state.zero_hp), @@ -146,21 +148,21 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(3, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Can't Get EXP", egui::Checkbox::without_text(&mut state.cant_get_exp), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Can't Evade", egui::Checkbox::without_text(&mut state.cant_evade), )) .changed(); - modified |= columns[2] + columns[2] .add(luminol_components::Field::new( "Slip Damage", egui::Checkbox::without_text(&mut state.slip_damage), @@ -171,14 +173,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Rating", egui::DragValue::new(&mut state.rating).clamp_range(0..=10), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "EVA", egui::DragValue::new(&mut state.eva), @@ -189,7 +191,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Max HP %", egui::Slider::new(&mut state.maxhp_rate, 0..=200) @@ -197,7 +199,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Max SP %", egui::Slider::new(&mut state.maxsp_rate, 0..=200) @@ -209,14 +211,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "STR %", egui::Slider::new(&mut state.str_rate, 0..=200).suffix("%"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "DEX %", egui::Slider::new(&mut state.dex_rate, 0..=200).suffix("%"), @@ -227,14 +229,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "AGI %", egui::Slider::new(&mut state.agi_rate, 0..=200).suffix("%"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "INT %", egui::Slider::new(&mut state.int_rate, 0..=200).suffix("%"), @@ -245,14 +247,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Hit Rate %", egui::Slider::new(&mut state.hit_rate, 0..=200).suffix("%"), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "ATK %", egui::Slider::new(&mut state.atk_rate, 0..=200).suffix("%"), @@ -263,7 +265,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "PDEF %", egui::Slider::new(&mut state.pdef_rate, 0..=200) @@ -271,7 +273,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "MDEF %", egui::Slider::new(&mut state.mdef_rate, 0..=200) @@ -283,7 +285,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Auto Release Probability", egui::Slider::new(&mut state.auto_release_prob, 0..=100) @@ -291,7 +293,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Auto Release Interval", egui::DragValue::new(&mut state.hold_turn) @@ -303,7 +305,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Damage Release Probability", egui::Slider::new(&mut state.shock_release_prob, 0..=100) @@ -311,7 +313,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Battle Only", egui::Checkbox::without_text(&mut state.battle_only), @@ -338,7 +340,7 @@ impl luminol_core::Window for Window { if self.previous_state != Some(state.id) { selection.clear_search(); } - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Element Defense", selection, @@ -366,7 +368,7 @@ impl luminol_core::Window for Window { if self.previous_state != Some(state.id) { selection.clear_search(); } - modified |= columns[1] + columns[1] .add(luminol_components::Field::new("State Change", selection)) .changed(); }); @@ -378,13 +380,6 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { - modified = true; - } - - if modified { - update_state.modified.set(true); - states.modified = true; - } + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} } } diff --git a/crates/ui/src/windows/weapons.rs b/crates/ui/src/windows/weapons.rs index b75ce381..9c2fb819 100644 --- a/crates/ui/src/windows/weapons.rs +++ b/crates/ui/src/windows/weapons.rs @@ -24,8 +24,8 @@ use luminol_components::UiExt; -#[derive(Default)] pub struct Window { + weapons: Vec, selected_weapon_name: Option, previous_weapon: Option, @@ -33,8 +33,15 @@ pub struct Window { } impl Window { - pub fn new() -> Self { - Default::default() + pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { + let weapons = update_state.data.weapons(); + Self { + weapons: weapons.data.clone(), + selected_weapon_name: None, + previous_weapon: None, + + view: luminol_components::DatabaseView::new(), + } } } @@ -61,13 +68,6 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut weapons = update_state.data.weapons(); - let animations = update_state.data.animations(); - let system = update_state.data.system(); - let states = update_state.data.states(); - - let mut modified = false; - self.selected_weapon_name = None; let response = egui::Window::new(self.name()) @@ -79,33 +79,35 @@ impl luminol_core::Window for Window { ui, update_state, "Weapons", - &mut weapons.data, + &mut self.weapons, |weapon| format!("{:0>4}: {}", weapon.id + 1, weapon.name), - |ui, weapons, id| { + |ui, weapons, id, update_state| { let weapon = &mut weapons[id]; self.selected_weapon_name = Some(weapon.name.clone()); + let animations = update_state.data.animations(); + let system = update_state.data.system(); + let states = update_state.data.states(); + ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut weapon.name) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut weapon.name) + .desired_width(f32::INFINITY), + )) + .changed(); - modified |= ui - .add(luminol_components::Field::new( - "Description", - egui::TextEdit::multiline(&mut weapon.description) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.add(luminol_components::Field::new( + "Description", + egui::TextEdit::multiline(&mut weapon.description) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "User Animation", luminol_components::OptionalIdComboBox::new( @@ -123,7 +125,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "Target Animation", luminol_components::OptionalIdComboBox::new( @@ -145,7 +147,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(4, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "Price", egui::DragValue::new(&mut weapon.price) @@ -153,7 +155,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "ATK", egui::DragValue::new(&mut weapon.atk) @@ -161,7 +163,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[2] + columns[2] .add(luminol_components::Field::new( "PDEF", egui::DragValue::new(&mut weapon.pdef) @@ -169,7 +171,7 @@ impl luminol_core::Window for Window { )) .changed(); - modified |= columns[3] + columns[3] .add(luminol_components::Field::new( "MDEF", egui::DragValue::new(&mut weapon.mdef) @@ -181,28 +183,28 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(4, |columns| { - modified |= columns[0] + columns[0] .add(luminol_components::Field::new( "STR+", egui::DragValue::new(&mut weapon.str_plus), )) .changed(); - modified |= columns[1] + columns[1] .add(luminol_components::Field::new( "DEX+", egui::DragValue::new(&mut weapon.dex_plus), )) .changed(); - modified |= columns[2] + columns[2] .add(luminol_components::Field::new( "AGI+", egui::DragValue::new(&mut weapon.agi_plus), )) .changed(); - modified |= columns[3] + columns[3] .add(luminol_components::Field::new( "INT+", egui::DragValue::new(&mut weapon.int_plus), @@ -228,7 +230,7 @@ impl luminol_core::Window for Window { if self.previous_weapon != Some(weapon.id) { selection.clear_search(); } - modified |= columns[0] + columns[0] .add(luminol_components::Field::new("Elements", selection)) .changed(); @@ -249,7 +251,7 @@ impl luminol_core::Window for Window { if self.previous_weapon != Some(weapon.id) { selection.clear_search(); } - modified |= columns[1] + columns[1] .add(luminol_components::Field::new("State Change", selection)) .changed(); }); @@ -260,13 +262,6 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { - modified = true; - } - - if modified { - update_state.modified.set(true); - weapons.modified = true; - } + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} } } diff --git a/src/app/top_bar.rs b/src/app/top_bar.rs index f820ecb0..2aa1a006 100644 --- a/src/app/top_bar.rs +++ b/src/app/top_bar.rs @@ -211,31 +211,31 @@ impl TopBar { if ui.button("Items").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::items::Window::new()); + .add_window(luminol_ui::windows::items::Window::new(update_state)); } if ui.button("Skills").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::skills::Window::new()); + .add_window(luminol_ui::windows::skills::Window::new(update_state)); } if ui.button("Weapons").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::weapons::Window::new()); + .add_window(luminol_ui::windows::weapons::Window::new(update_state)); } if ui.button("Armor").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::armor::Window::new()); + .add_window(luminol_ui::windows::armor::Window::new(update_state)); } if ui.button("States").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::states::Window::new()); + .add_window(luminol_ui::windows::states::Window::new(update_state)); } ui.separator(); @@ -243,19 +243,19 @@ impl TopBar { if ui.button("Actors").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::actors::Window::new()); + .add_window(luminol_ui::windows::actors::Window::new(update_state)); } if ui.button("Classes").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::classes::Window::new()); + .add_window(luminol_ui::windows::classes::Window::new(update_state)); } if ui.button("Enemies").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::enemies::Window::new()); + .add_window(luminol_ui::windows::enemies::Window::new(update_state)); } ui.add_enabled_ui(false, |ui| { From b1f223bc92532ce8bbf34584aaf6d89d62245b1f Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 19:30:31 -0700 Subject: [PATCH 40/67] Revert "Very destructive commit" This reverts commit 471b014c55908fe9de0a362142e265f005428aa3. --- crates/components/src/database_view.rs | 6 +- crates/data/src/rmxp/actor.rs | 2 +- crates/data/src/rmxp/armor.rs | 2 +- crates/data/src/rmxp/class.rs | 4 +- crates/data/src/rmxp/enemy.rs | 4 +- crates/data/src/rmxp/skill.rs | 2 +- crates/data/src/rmxp/state.rs | 2 +- crates/data/src/rmxp/weapon.rs | 2 +- crates/modals/src/sound_picker.rs | 50 ++-- crates/ui/src/windows/actors.rs | 112 ++++----- crates/ui/src/windows/armor.rs | 84 ++++--- crates/ui/src/windows/classes.rs | 147 +++++------ crates/ui/src/windows/enemies.rs | 322 +++++++++++++------------ crates/ui/src/windows/items.rs | 137 ++++++----- crates/ui/src/windows/skills.rs | 106 ++++---- crates/ui/src/windows/states.rs | 97 ++++---- crates/ui/src/windows/weapons.rs | 87 +++---- src/app/top_bar.rs | 16 +- 18 files changed, 614 insertions(+), 568 deletions(-) diff --git a/crates/components/src/database_view.rs b/crates/components/src/database_view.rs index c3832dc3..6ebad57c 100644 --- a/crates/components/src/database_view.rs +++ b/crates/components/src/database_view.rs @@ -48,11 +48,11 @@ impl DatabaseView { pub fn show( &mut self, ui: &mut egui::Ui, - update_state: &mut luminol_core::UpdateState<'_>, + update_state: &luminol_core::UpdateState<'_>, label: impl Into, vec: &mut Vec, formatter: impl Fn(&T) -> String, - inner: impl FnOnce(&mut egui::Ui, &mut Vec, usize, &mut luminol_core::UpdateState<'_>) -> R, + inner: impl FnOnce(&mut egui::Ui, &mut Vec, usize) -> R, ) -> egui::InnerResponse> where T: luminol_data::rpg::DatabaseEntry, @@ -262,7 +262,7 @@ impl DatabaseView { DatabaseViewResponse { inner: (self.selected_id < vec.len()) - .then(|| inner(ui, vec, self.selected_id, update_state)), + .then(|| inner(ui, vec, self.selected_id)), modified, } }) diff --git a/crates/data/src/rmxp/actor.rs b/crates/data/src/rmxp/actor.rs index 14dcd809..256507a5 100644 --- a/crates/data/src/rmxp/actor.rs +++ b/crates/data/src/rmxp/actor.rs @@ -19,7 +19,7 @@ use crate::{ optional_path_serde, Path, Table2, }; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Actor")] pub struct Actor { diff --git a/crates/data/src/rmxp/armor.rs b/crates/data/src/rmxp/armor.rs index 0fd745ce..35413abf 100644 --- a/crates/data/src/rmxp/armor.rs +++ b/crates/data/src/rmxp/armor.rs @@ -19,7 +19,7 @@ use crate::{ optional_path_alox, optional_path_serde, Path, }; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Armor")] pub struct Armor { diff --git a/crates/data/src/rmxp/class.rs b/crates/data/src/rmxp/class.rs index 4ae00d87..667d3859 100644 --- a/crates/data/src/rmxp/class.rs +++ b/crates/data/src/rmxp/class.rs @@ -16,7 +16,7 @@ // along with Luminol. If not, see . pub use crate::{id_alox, id_serde, id_vec_alox, id_vec_serde, Table1}; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Class")] pub struct Class { @@ -36,7 +36,7 @@ pub struct Class { pub learnings: Vec, } -#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Class::Learning")] pub struct Learning { diff --git a/crates/data/src/rmxp/enemy.rs b/crates/data/src/rmxp/enemy.rs index e19269e8..363cda58 100644 --- a/crates/data/src/rmxp/enemy.rs +++ b/crates/data/src/rmxp/enemy.rs @@ -19,7 +19,7 @@ pub use crate::{ optional_path_serde, Path, Table1, }; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Enemy")] pub struct Enemy { @@ -65,7 +65,7 @@ pub struct Enemy { pub treasure_prob: i32, } -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Enemy::Action")] pub struct Action { diff --git a/crates/data/src/rmxp/skill.rs b/crates/data/src/rmxp/skill.rs index 32daf3aa..820e7efd 100644 --- a/crates/data/src/rmxp/skill.rs +++ b/crates/data/src/rmxp/skill.rs @@ -19,7 +19,7 @@ pub use crate::{ optional_path_alox, optional_path_serde, rpg::AudioFile, Path, }; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Skill")] pub struct Skill { diff --git a/crates/data/src/rmxp/state.rs b/crates/data/src/rmxp/state.rs index d017ae41..ffc4f442 100644 --- a/crates/data/src/rmxp/state.rs +++ b/crates/data/src/rmxp/state.rs @@ -16,7 +16,7 @@ // along with Luminol. If not, see . use crate::{id_alox, id_serde, id_vec_alox, id_vec_serde, optional_id_alox, optional_id_serde}; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::State")] pub struct State { diff --git a/crates/data/src/rmxp/weapon.rs b/crates/data/src/rmxp/weapon.rs index c2dffd31..52774f96 100644 --- a/crates/data/src/rmxp/weapon.rs +++ b/crates/data/src/rmxp/weapon.rs @@ -19,7 +19,7 @@ pub use crate::{ optional_path_alox, optional_path_serde, rpg::AudioFile, Path, }; -#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] +#[derive(Default, Debug, serde::Deserialize, serde::Serialize)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::Weapon")] pub struct Weapon { diff --git a/crates/modals/src/sound_picker.rs b/crates/modals/src/sound_picker.rs index 217cd108..57054d20 100644 --- a/crates/modals/src/sound_picker.rs +++ b/crates/modals/src/sound_picker.rs @@ -24,23 +24,18 @@ use luminol_core::prelude::*; pub struct Modal { - state: State, - id_source: egui::Id, - source: Source, -} - -enum State { - Closed, - Open { tab: luminol_components::SoundTab }, + tab: luminol_components::SoundTab, + open: bool, } impl Modal { - pub fn new(source: Source, id_source: impl Into) -> Self { - Self { - state: State::Closed, - id_source: id_source.into(), - source, - } + pub fn new( + filesystem: &impl FileSystem, + source: Source, + selected_track: luminol_data::rpg::AudioFile, + ) -> Self { + let tab = luminol_components::SoundTab::new(filesystem, source, selected_track); + Self { tab, open: false } } } @@ -53,8 +48,8 @@ impl luminol_core::Modal for Modal { update_state: &'m mut luminol_core::UpdateState<'_>, ) -> impl egui::Widget + 'm { |ui: &mut egui::Ui| { - let button_text = if let Some(track) = &data.name { - format!("Audio/{}/{}", self.source, track) + let button_text = if let Some(track) = &self.tab.audio_file.name { + format!("Audio/{}/{}", self.tab.source, track) } else { "(None)".to_string() }; @@ -62,12 +57,7 @@ impl luminol_core::Modal for Modal { let mut button_response = ui.button(button_text); if button_response.clicked() { - let tab = luminol_components::SoundTab::new( - update_state.filesystem, - self.source, - data.clone(), - ); - self.state = State::Open { tab }; + self.open = true; } if self.show_window(update_state, ui.ctx(), data) { button_response.mark_changed() @@ -78,7 +68,7 @@ impl luminol_core::Modal for Modal { } fn reset(&mut self) { - self.state = State::Closed; + self.open = false; } } @@ -89,30 +79,24 @@ impl Modal { ctx: &egui::Context, data: &mut luminol_data::rpg::AudioFile, ) -> bool { - let mut win_open = true; + let mut win_open = self.open; let mut keep_open = true; let mut needs_save = false; - let State::Open { tab } = &mut self.state else { - return false; - }; - egui::Window::new("Graphic Picker") .open(&mut win_open) .show(ctx, |ui| { - tab.ui(ui, update_state); + self.tab.ui(ui, update_state); ui.separator(); luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); }); if needs_save { - *data = tab.audio_file.clone(); + *data = self.tab.audio_file.clone(); } - if !(win_open && keep_open) { - self.state = State::Closed; - } + self.open = win_open && keep_open; needs_save } } diff --git a/crates/ui/src/windows/actors.rs b/crates/ui/src/windows/actors.rs index bbd460da..dbe35e03 100644 --- a/crates/ui/src/windows/actors.rs +++ b/crates/ui/src/windows/actors.rs @@ -27,8 +27,8 @@ use luminol_components::UiExt; use luminol_data::rpg::armor::Kind; +#[derive(Default)] pub struct Window { - actors: Vec, selected_actor_name: Option, previous_actor: Option, @@ -39,16 +39,8 @@ pub struct Window { } impl Window { - pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { - let actors = update_state.data.actors(); - Self { - actors: actors.data.clone(), - selected_actor_name: None, - previous_actor: None, - exp_view_is_total: false, - exp_view_is_depersisted: false, - view: luminol_components::DatabaseView::new(), - } + pub fn new() -> Self { + Default::default() } } @@ -230,6 +222,13 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { + let mut actors = update_state.data.actors(); + let mut classes = update_state.data.classes(); + let weapons = update_state.data.weapons(); + let armors = update_state.data.armors(); + + let mut modified = false; + self.selected_actor_name = None; let response = egui::Window::new(self.name()) @@ -241,42 +240,40 @@ impl luminol_core::Window for Window { ui, update_state, "Actors", - &mut self.actors, + &mut actors.data, |actor| format!("{:0>4}: {}", actor.id + 1, actor.name), - |ui, actors, id, update_state| { + |ui, actors, id| { let actor = &mut actors[id]; self.selected_actor_name = Some(actor.name.clone()); - let mut classes = update_state.data.classes(); - let weapons = update_state.data.weapons(); - let armors = update_state.data.armors(); - ui.with_padded_stripe(false, |ui| { - ui.add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut actor.name) - .desired_width(f32::INFINITY), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut actor.name) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { - ui.add(luminol_components::Field::new( - "Class", - luminol_components::OptionalIdComboBox::new( - update_state, - (actor.id, "class"), - &mut actor.class_id, - 0..classes.data.len(), - |id| { - classes.data.get(id).map_or_else( - || "".into(), - |c| format!("{:0>4}: {}", id + 1, c.name), - ) - }, - ), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Class", + luminol_components::OptionalIdComboBox::new( + update_state, + (actor.id, "class"), + &mut actor.class_id, + 0..classes.data.len(), + |id| { + classes.data.get(id).map_or_else( + || "".into(), + |c| format!("{:0>4}: {}", id + 1, c.name), + ) + }, + ), + )) + .changed(); }); if let Some(class) = classes.data.get_mut(actor.class_id) { @@ -296,7 +293,7 @@ impl luminol_core::Window for Window { egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add( luminol_components::OptionalIdComboBox::new( update_state, @@ -328,7 +325,7 @@ impl luminol_core::Window for Window { ), ) .changed(); - columns[1] + modified |= columns[1] .checkbox(&mut actor.weapon_fix, "Fixed") .changed(); }); @@ -345,7 +342,7 @@ impl luminol_core::Window for Window { egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add( luminol_components::OptionalIdComboBox::new( update_state, @@ -384,7 +381,7 @@ impl luminol_core::Window for Window { ), ) .changed(); - columns[1] + modified |= columns[1] .checkbox(&mut actor.armor1_fix, "Fixed") .changed(); }); @@ -401,7 +398,7 @@ impl luminol_core::Window for Window { egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add( luminol_components::OptionalIdComboBox::new( update_state, @@ -440,7 +437,7 @@ impl luminol_core::Window for Window { ), ) .changed(); - columns[1] + modified |= columns[1] .checkbox(&mut actor.armor2_fix, "Fixed") .changed(); }); @@ -457,7 +454,7 @@ impl luminol_core::Window for Window { egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add( luminol_components::OptionalIdComboBox::new( update_state, @@ -496,7 +493,7 @@ impl luminol_core::Window for Window { ), ) .changed(); - columns[1] + modified |= columns[1] .checkbox(&mut actor.armor3_fix, "Fixed") .changed(); }); @@ -513,7 +510,7 @@ impl luminol_core::Window for Window { egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add( luminol_components::OptionalIdComboBox::new( update_state, @@ -552,7 +549,7 @@ impl luminol_core::Window for Window { ), ) .changed(); - columns[1] + modified |= columns[1] .checkbox(&mut actor.armor4_fix, "Fixed") .changed(); }); @@ -564,14 +561,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Initial Level", egui::Slider::new(&mut actor.initial_level, 1..=99), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Final Level", egui::Slider::new( @@ -613,14 +610,14 @@ impl luminol_core::Window for Window { }); ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "EXP Curve Basis", egui::Slider::new(&mut actor.exp_basis, 10..=50), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "EXP Curve Inflation", egui::Slider::new(&mut actor.exp_inflation, 10..=50), @@ -724,6 +721,13 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { + modified = true; + } + + if modified { + update_state.modified.set(true); + actors.modified = true; + } } } diff --git a/crates/ui/src/windows/armor.rs b/crates/ui/src/windows/armor.rs index 61ef5498..e1d0eb50 100644 --- a/crates/ui/src/windows/armor.rs +++ b/crates/ui/src/windows/armor.rs @@ -24,8 +24,8 @@ use luminol_components::UiExt; +#[derive(Default)] pub struct Window { - armors: Vec, selected_armor_name: Option, previous_armor: Option, @@ -33,14 +33,8 @@ pub struct Window { } impl Window { - pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { - let armors = update_state.data.armors(); - Self { - armors: armors.data.clone(), - selected_armor_name: None, - previous_armor: None, - view: luminol_components::DatabaseView::new(), - } + pub fn new() -> Self { + Default::default() } } @@ -67,6 +61,12 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { + let mut armors = update_state.data.armors(); + let system = update_state.data.system(); + let states = update_state.data.states(); + + let mut modified = false; + self.selected_armor_name = None; let response = egui::Window::new(self.name()) @@ -78,34 +78,33 @@ impl luminol_core::Window for Window { ui, update_state, "Armor", - &mut self.armors, + &mut armors.data, |armor| format!("{:0>4}: {}", armor.id + 1, armor.name), - |ui, armors, id, update_state| { + |ui, armors, id| { let armor = &mut armors[id]; self.selected_armor_name = Some(armor.name.clone()); - let system = update_state.data.system(); - let states = update_state.data.states(); - ui.with_padded_stripe(false, |ui| { - ui.add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut armor.name) - .desired_width(f32::INFINITY), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut armor.name) + .desired_width(f32::INFINITY), + )) + .changed(); - ui.add(luminol_components::Field::new( - "Description", - egui::TextEdit::multiline(&mut armor.description) - .desired_width(f32::INFINITY), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Description", + egui::TextEdit::multiline(&mut armor.description) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Kind", luminol_components::EnumComboBox::new( @@ -115,7 +114,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Auto State", luminol_components::OptionalIdComboBox::new( @@ -137,7 +136,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(4, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Price", egui::DragValue::new(&mut armor.price) @@ -145,7 +144,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "EVA", egui::DragValue::new(&mut armor.eva) @@ -153,7 +152,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[2] + modified |= columns[2] .add(luminol_components::Field::new( "PDEF", egui::DragValue::new(&mut armor.pdef) @@ -161,7 +160,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[3] + modified |= columns[3] .add(luminol_components::Field::new( "MDEF", egui::DragValue::new(&mut armor.mdef) @@ -173,28 +172,28 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(4, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "STR+", egui::DragValue::new(&mut armor.str_plus), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "DEX+", egui::DragValue::new(&mut armor.dex_plus), )) .changed(); - columns[2] + modified |= columns[2] .add(luminol_components::Field::new( "AGI+", egui::DragValue::new(&mut armor.agi_plus), )) .changed(); - columns[3] + modified |= columns[3] .add(luminol_components::Field::new( "INT+", egui::DragValue::new(&mut armor.int_plus), @@ -220,7 +219,7 @@ impl luminol_core::Window for Window { if self.previous_armor != Some(armor.id) { selection.clear_search(); } - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Element Defense", selection, @@ -242,7 +241,7 @@ impl luminol_core::Window for Window { if self.previous_armor != Some(armor.id) { selection.clear_search(); } - columns[1] + modified |= columns[1] .add(luminol_components::Field::new("States", selection)) .changed(); }); @@ -253,6 +252,13 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { + modified = true; + } + + if modified { + update_state.modified.set(true); + armors.modified = true; + } } } diff --git a/crates/ui/src/windows/classes.rs b/crates/ui/src/windows/classes.rs index da70b4b0..835b575a 100644 --- a/crates/ui/src/windows/classes.rs +++ b/crates/ui/src/windows/classes.rs @@ -24,8 +24,8 @@ use luminol_components::UiExt; +#[derive(Default)] pub struct Window { - classes: Vec, selected_class_name: Option, previous_class: Option, @@ -34,16 +34,8 @@ pub struct Window { } impl Window { - pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { - let classes = update_state.data.classes(); - - Self { - classes: classes.data.clone(), - selected_class_name: None, - previous_class: None, - collapsing_view: luminol_components::CollapsingView::new(), - view: luminol_components::DatabaseView::new(), - } + pub fn new() -> Self { + Default::default() } fn show_learning_header( @@ -66,18 +58,19 @@ impl Window { learning: (usize, &mut luminol_data::rpg::class::Learning), ) -> egui::Response { let (learning_index, learning) = learning; + let mut modified = false; - egui::Frame::none() + let mut response = egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Level", egui::Slider::new(&mut learning.level, 1..=99), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Skill", luminol_components::OptionalIdComboBox::new( @@ -96,7 +89,12 @@ impl Window { .changed(); }); }) - .response + .response; + + if modified { + response.mark_changed(); + } + response } } @@ -123,6 +121,15 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { + let mut classes = update_state.data.classes(); + let system = update_state.data.system(); + let states = update_state.data.states(); + let skills = update_state.data.skills(); + let weapons = update_state.data.weapons(); + let armors = update_state.data.armors(); + + let mut modified = false; + self.selected_class_name = None; let response = egui::Window::new(self.name()) @@ -134,65 +141,62 @@ impl luminol_core::Window for Window { ui, update_state, "Classes", - &mut self.classes, + &mut classes.data, |class| format!("{:0>3}: {}", class.id + 1, class.name), - |ui, classes, id, update_state| { + |ui, classes, id| { let class = &mut classes[id]; self.selected_class_name = Some(class.name.clone()); - let system = update_state.data.system(); - let states = update_state.data.states(); - let skills = update_state.data.skills(); - let weapons = update_state.data.weapons(); - let armors = update_state.data.armors(); - ui.with_padded_stripe(false, |ui| { - ui.add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut class.name) - .desired_width(f32::INFINITY), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut class.name) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { - ui.add(luminol_components::Field::new( - "Position", - luminol_components::EnumComboBox::new( - (class.id, "position"), - &mut class.position, - ), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Position", + luminol_components::EnumComboBox::new( + (class.id, "position"), + &mut class.position, + ), + )) + .changed(); }); ui.with_padded_stripe(false, |ui| { - ui.add(luminol_components::Field::new( - "Skills", - |ui: &mut egui::Ui| { - if self.previous_class != Some(class.id) { - self.collapsing_view.clear_animations(); - } - self.collapsing_view.show( - ui, - class.id, - &mut class.learnings, - |ui, _i, learning| { - Self::show_learning_header(ui, &skills, learning) - }, - |ui, i, learning| { - Self::show_learning_body( - ui, - update_state, - &skills, - class.id, - (i, learning), - ) - }, - ) - }, - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Skills", + |ui: &mut egui::Ui| { + if self.previous_class != Some(class.id) { + self.collapsing_view.clear_animations(); + } + self.collapsing_view.show( + ui, + class.id, + &mut class.learnings, + |ui, _i, learning| { + Self::show_learning_header(ui, &skills, learning) + }, + |ui, i, learning| { + Self::show_learning_body( + ui, + update_state, + &skills, + class.id, + (i, learning), + ) + }, + ) + }, + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { @@ -212,7 +216,7 @@ impl luminol_core::Window for Window { if self.previous_class != Some(class.id) { selection.clear_search(); } - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Equippable Weapons", selection, @@ -234,7 +238,7 @@ impl luminol_core::Window for Window { if self.previous_class != Some(class.id) { selection.clear_search(); } - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Equippable Armor", selection, @@ -262,7 +266,7 @@ impl luminol_core::Window for Window { if self.previous_class != Some(class.id) { selection.clear_search(); } - columns[0] + modified |= columns[0] .add(luminol_components::Field::new("Elements", selection)) .changed(); @@ -283,7 +287,7 @@ impl luminol_core::Window for Window { if self.previous_class != Some(class.id) { selection.clear_search(); } - columns[1] + modified |= columns[1] .add(luminol_components::Field::new("States", selection)) .changed(); }); @@ -294,6 +298,13 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { + modified = true; + } + + if modified { + update_state.modified.set(true); + classes.modified = true; + } } } diff --git a/crates/ui/src/windows/enemies.rs b/crates/ui/src/windows/enemies.rs index 836a754f..08e0c9e4 100644 --- a/crates/ui/src/windows/enemies.rs +++ b/crates/ui/src/windows/enemies.rs @@ -35,8 +35,8 @@ pub enum TreasureType { Armor, } +#[derive(Default)] pub struct Window { - enemies: Vec, selected_enemy_name: Option, previous_enemy: Option, @@ -45,15 +45,8 @@ pub struct Window { } impl Window { - pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { - let enimies = update_state.data.enemies(); - Self { - enemies: enimies.data.clone(), - selected_enemy_name: None, - previous_enemy: None, - collapsing_view: luminol_components::CollapsingView::new(), - view: luminol_components::DatabaseView::new(), - } + pub fn new() -> Self { + Default::default() } fn show_action_header( @@ -116,11 +109,12 @@ impl Window { action: (usize, &mut luminol_data::rpg::enemy::Action), ) -> egui::Response { let (action_index, action) = action; + let mut modified = false; - egui::Frame::none() + let mut response = egui::Frame::none() .show(ui, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Turn Offset", egui::DragValue::new(&mut action.condition_turn_a) @@ -128,7 +122,7 @@ impl Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Turn Interval", egui::DragValue::new(&mut action.condition_turn_b) @@ -138,14 +132,14 @@ impl Window { }); ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Max HP %", egui::Slider::new(&mut action.condition_hp, 0..=100).suffix("%"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Min Level", egui::Slider::new(&mut action.condition_level, 1..=99), @@ -153,25 +147,26 @@ impl Window { .changed(); }); - ui.add(luminol_components::Field::new( - "Switch", - luminol_components::OptionalIdComboBox::new( - update_state, - (enemy_id, action_index, "condition_switch_id"), - &mut action.condition_switch_id, - 0..system.switches.len(), - |id| { - system - .switches - .get(id) - .map_or_else(|| "".into(), |s| format!("{:0>4}: {}", id + 1, s)) - }, - ), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Switch", + luminol_components::OptionalIdComboBox::new( + update_state, + (enemy_id, action_index, "condition_switch_id"), + &mut action.condition_switch_id, + 0..system.switches.len(), + |id| { + system + .switches + .get(id) + .map_or_else(|| "".into(), |s| format!("{:0>4}: {}", id + 1, s)) + }, + ), + )) + .changed(); ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Kind", luminol_components::EnumComboBox::new( @@ -183,7 +178,7 @@ impl Window { match action.kind { luminol_data::rpg::enemy::Kind::Basic => { - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Basic Type", luminol_components::EnumComboBox::new( @@ -194,7 +189,7 @@ impl Window { .changed(); } luminol_data::rpg::enemy::Kind::Skill => { - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Skill", luminol_components::OptionalIdComboBox::new( @@ -215,13 +210,19 @@ impl Window { } }); - ui.add(luminol_components::Field::new( - "Rating", - egui::Slider::new(&mut action.rating, 1..=10), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Rating", + egui::Slider::new(&mut action.rating, 1..=10), + )) + .changed(); }) - .response + .response; + + if modified { + response.mark_changed(); + } + response } } @@ -248,6 +249,17 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { + let mut enemies = update_state.data.enemies(); + let animations = update_state.data.animations(); + let system = update_state.data.system(); + let states = update_state.data.states(); + let skills = update_state.data.skills(); + let items = update_state.data.items(); + let weapons = update_state.data.weapons(); + let armors = update_state.data.armors(); + + let mut modified = false; + self.selected_enemy_name = None; let response = egui::Window::new(self.name()) @@ -259,32 +271,25 @@ impl luminol_core::Window for Window { ui, update_state, "Enemies", - &mut self.enemies, + &mut enemies.data, |enemy| format!("{:0>4}: {}", enemy.id + 1, enemy.name), - |ui, enemies, id, update_state| { + |ui, enemies, id| { let enemy = &mut enemies[id]; self.selected_enemy_name = Some(enemy.name.clone()); - let animations = update_state.data.animations(); - let system = update_state.data.system(); - let states = update_state.data.states(); - let skills = update_state.data.skills(); - let items = update_state.data.items(); - let weapons = update_state.data.weapons(); - let armors = update_state.data.armors(); - ui.with_padded_stripe(false, |ui| { - ui.add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut enemy.name) - .desired_width(f32::INFINITY), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut enemy.name) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Attacker Animation", luminol_components::OptionalIdComboBox::new( @@ -302,7 +307,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Target Animation", luminol_components::OptionalIdComboBox::new( @@ -324,7 +329,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(4, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "EXP", egui::DragValue::new(&mut enemy.exp) @@ -332,7 +337,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Gold", egui::DragValue::new(&mut enemy.gold) @@ -340,7 +345,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[2] + modified |= columns[2] .add(luminol_components::Field::new( "Max HP", egui::DragValue::new(&mut enemy.maxhp) @@ -348,7 +353,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[3] + modified |= columns[3] .add(luminol_components::Field::new( "Max SP", egui::DragValue::new(&mut enemy.maxsp) @@ -360,7 +365,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(4, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "STR", egui::DragValue::new(&mut enemy.str) @@ -368,7 +373,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "DEX", egui::DragValue::new(&mut enemy.dex) @@ -376,7 +381,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[2] + modified |= columns[2] .add(luminol_components::Field::new( "AGI", egui::DragValue::new(&mut enemy.agi) @@ -384,7 +389,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[3] + modified |= columns[3] .add(luminol_components::Field::new( "INT", egui::DragValue::new(&mut enemy.int) @@ -396,7 +401,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(4, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "ATK", egui::DragValue::new(&mut enemy.atk) @@ -404,7 +409,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "EVA", egui::DragValue::new(&mut enemy.eva) @@ -412,7 +417,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[2] + modified |= columns[2] .add(luminol_components::Field::new( "PDEF", egui::DragValue::new(&mut enemy.pdef) @@ -420,7 +425,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[3] + modified |= columns[3] .add(luminol_components::Field::new( "MDEF", egui::DragValue::new(&mut enemy.mdef) @@ -442,7 +447,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Treasure Type", luminol_components::EnumComboBox::new( @@ -452,7 +457,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Treasure Probability", egui::Slider::new(&mut enemy.treasure_prob, 0..=100) @@ -474,23 +479,24 @@ impl luminol_core::Window for Window { if enemy.item_id.is_none() { enemy.item_id = Some(0); } - ui.add(luminol_components::Field::new( - "Treasure", - luminol_components::OptionalIdComboBox::new( - update_state, - (enemy.id, "item_id"), - &mut enemy.item_id, - 0..items.data.len(), - |id| { - items.data.get(id).map_or_else( - || "".into(), - |i| format!("{:0>4}: {}", id + 1, i.name), - ) - }, - ) - .allow_none(false), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Treasure", + luminol_components::OptionalIdComboBox::new( + update_state, + (enemy.id, "item_id"), + &mut enemy.item_id, + 0..items.data.len(), + |id| { + items.data.get(id).map_or_else( + || "".into(), + |i| format!("{:0>4}: {}", id + 1, i.name), + ) + }, + ) + .allow_none(false), + )) + .changed(); } TreasureType::Weapon => { @@ -499,23 +505,24 @@ impl luminol_core::Window for Window { if enemy.weapon_id.is_none() { enemy.weapon_id = Some(0); } - ui.add(luminol_components::Field::new( - "Treasure", - luminol_components::OptionalIdComboBox::new( - update_state, - (enemy.id, "weapon_id"), - &mut enemy.weapon_id, - 0..weapons.data.len(), - |id| { - weapons.data.get(id).map_or_else( - || "".into(), - |w| format!("{:0>4}: {}", id + 1, w.name), - ) - }, - ) - .allow_none(false), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Treasure", + luminol_components::OptionalIdComboBox::new( + update_state, + (enemy.id, "weapon_id"), + &mut enemy.weapon_id, + 0..weapons.data.len(), + |id| { + weapons.data.get(id).map_or_else( + || "".into(), + |w| format!("{:0>4}: {}", id + 1, w.name), + ) + }, + ) + .allow_none(false), + )) + .changed(); } TreasureType::Armor => { @@ -524,55 +531,57 @@ impl luminol_core::Window for Window { if enemy.armor_id.is_none() { enemy.armor_id = Some(0); } - ui.add(luminol_components::Field::new( - "Treasure", - luminol_components::OptionalIdComboBox::new( - update_state, - (enemy.id, "armor_id"), - &mut enemy.armor_id, - 0..armors.data.len(), - |id| { - armors.data.get(id).map_or_else( - || "".into(), - |a| format!("{:0>4}: {}", id + 1, a.name), - ) - }, - ) - .allow_none(false), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Treasure", + luminol_components::OptionalIdComboBox::new( + update_state, + (enemy.id, "armor_id"), + &mut enemy.armor_id, + 0..armors.data.len(), + |id| { + armors.data.get(id).map_or_else( + || "".into(), + |a| format!("{:0>4}: {}", id + 1, a.name), + ) + }, + ) + .allow_none(false), + )) + .changed(); } }; }); ui.with_padded_stripe(false, |ui| { - ui.add(luminol_components::Field::new( - "Actions", - |ui: &mut egui::Ui| { - if self.previous_enemy != Some(enemy.id) { - self.collapsing_view.clear_animations(); - } - self.collapsing_view.show( - ui, - enemy.id, - &mut enemy.actions, - |ui, _i, action| { - Self::show_action_header(ui, &skills, action) - }, - |ui, i, action| { - Self::show_action_body( - ui, - update_state, - &system, - &skills, - enemy.id, - (i, action), - ) - }, - ) - }, - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Actions", + |ui: &mut egui::Ui| { + if self.previous_enemy != Some(enemy.id) { + self.collapsing_view.clear_animations(); + } + self.collapsing_view.show( + ui, + enemy.id, + &mut enemy.actions, + |ui, _i, action| { + Self::show_action_header(ui, &skills, action) + }, + |ui, i, action| { + Self::show_action_body( + ui, + update_state, + &system, + &skills, + enemy.id, + (i, action), + ) + }, + ) + }, + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { @@ -594,7 +603,7 @@ impl luminol_core::Window for Window { if self.previous_enemy != Some(enemy.id) { selection.clear_search(); } - columns[0] + modified |= columns[0] .add(luminol_components::Field::new("Elements", selection)) .changed(); @@ -615,7 +624,7 @@ impl luminol_core::Window for Window { if self.previous_enemy != Some(enemy.id) { selection.clear_search(); } - columns[1] + modified |= columns[1] .add(luminol_components::Field::new("States", selection)) .changed(); }); @@ -626,6 +635,13 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { + modified = true; + } + + if modified { + update_state.modified.set(true); + enemies.modified = true; + } } } diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index 9a6d21c6..a1c3981b 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -23,15 +23,18 @@ // Program grant you additional permission to convey the resulting work. use luminol_components::UiExt; -use luminol_core::Modal; /// Database - Items management window. +#[derive(Default)] pub struct Window { - items: Vec, + // ? Items ? selected_item_name: Option, + // ? Icon Graphic Picker ? + _icon_picker: Option, + // ? Menu Sound Effect Picker ? - menu_se_picker: luminol_modals::sound_picker::Modal, + _menu_se_picker: Option, previous_item: Option, @@ -39,18 +42,8 @@ pub struct Window { } impl Window { - pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { - let items = update_state.data.items(); - Self { - items: items.data.clone(), - selected_item_name: None, - menu_se_picker: luminol_modals::sound_picker::Modal::new( - luminol_audio::Source::SE, - "item_editor_sound_picker", - ), - previous_item: None, - view: luminol_components::DatabaseView::new(), - } + pub fn new() -> Self { + Default::default() } } @@ -77,9 +70,17 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { + let mut items = update_state.data.items(); + let animations = update_state.data.animations(); + let common_events = update_state.data.common_events(); + let system = update_state.data.system(); + let states = update_state.data.states(); + + let mut modified = false; + self.selected_item_name = None; - egui::Window::new(self.name()) + let response = egui::Window::new(self.name()) .id(self.id()) .default_width(500.) .open(open) @@ -88,31 +89,33 @@ impl luminol_core::Window for Window { ui, update_state, "Items", - &mut self.items, + &mut items.data, |item| format!("{:0>4}: {}", item.id + 1, item.name), - |ui, items, id, update_state| { + |ui, items, id| { let item = &mut items[id]; self.selected_item_name = Some(item.name.clone()); ui.with_padded_stripe(false, |ui| { - ui.add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut item.name) - .desired_width(f32::INFINITY), - )) - .changed(); - - ui.add(luminol_components::Field::new( - "Description", - egui::TextEdit::multiline(&mut item.description) - .desired_width(f32::INFINITY), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut item.name) + .desired_width(f32::INFINITY), + )) + .changed(); + + modified |= ui + .add(luminol_components::Field::new( + "Description", + egui::TextEdit::multiline(&mut item.description) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Scope", luminol_components::EnumComboBox::new( @@ -122,7 +125,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Occasion", luminol_components::EnumComboBox::new( @@ -135,9 +138,8 @@ impl luminol_core::Window for Window { }); ui.with_padded_stripe(false, |ui| { - let animations = update_state.data.animations(); ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "User Animation", luminol_components::OptionalIdComboBox::new( @@ -155,7 +157,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Target Animation", luminol_components::OptionalIdComboBox::new( @@ -177,15 +179,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Menu Use SE", - self.menu_se_picker.button(&mut item.menu_se, update_state), + egui::Label::new("TODO"), )) .changed(); - let common_events = update_state.data.common_events(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Common Event", luminol_components::OptionalIdComboBox::new( @@ -207,7 +208,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Price", egui::DragValue::new(&mut item.price) @@ -215,7 +216,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Consumable", egui::Checkbox::without_text(&mut item.consumable), @@ -226,17 +227,18 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { if item.parameter_type.is_none() { - ui.add(luminol_components::Field::new( - "Parameter", - luminol_components::EnumComboBox::new( - "parameter_type", - &mut item.parameter_type, - ), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Parameter", + luminol_components::EnumComboBox::new( + "parameter_type", + &mut item.parameter_type, + ), + )) + .changed(); } else { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Parameter", luminol_components::EnumComboBox::new( @@ -246,7 +248,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Parameter Increment", egui::DragValue::new(&mut item.parameter_points) @@ -259,7 +261,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Recover HP %", egui::Slider::new(&mut item.recover_hp_rate, 0..=100) @@ -267,7 +269,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Recover HP Points", egui::DragValue::new(&mut item.recover_hp) @@ -279,7 +281,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Recover SP %", egui::Slider::new(&mut item.recover_sp_rate, 0..=100) @@ -287,7 +289,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Recover SP Points", egui::DragValue::new(&mut item.recover_sp) @@ -299,14 +301,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Hit Rate", egui::Slider::new(&mut item.hit, 0..=100).suffix("%"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Variance", egui::Slider::new(&mut item.variance, 0..=100).suffix("%"), @@ -317,14 +319,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "PDEF-F", egui::Slider::new(&mut item.pdef_f, 0..=100).suffix("%"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "MDEF-F", egui::Slider::new(&mut item.mdef_f, 0..=100).suffix("%"), @@ -334,8 +336,6 @@ impl luminol_core::Window for Window { }); ui.with_padded_stripe(false, |ui| { - let system = update_state.data.system(); - let states = update_state.data.states(); ui.columns(2, |columns| { let mut selection = luminol_components::IdVecSelection::new( update_state, @@ -352,7 +352,7 @@ impl luminol_core::Window for Window { if self.previous_item != Some(item.id) { selection.clear_search(); } - columns[0] + modified |= columns[0] .add(luminol_components::Field::new("Elements", selection)) .changed(); @@ -373,7 +373,7 @@ impl luminol_core::Window for Window { if self.previous_item != Some(item.id) { selection.clear_search(); } - columns[1] + modified |= columns[1] .add(luminol_components::Field::new("State Change", selection)) .changed(); }); @@ -383,5 +383,14 @@ impl luminol_core::Window for Window { }, ) }); + + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { + modified = true; + } + + if modified { + update_state.modified.set(true); + items.modified = true; + } } } diff --git a/crates/ui/src/windows/skills.rs b/crates/ui/src/windows/skills.rs index 58a8694a..0d194751 100644 --- a/crates/ui/src/windows/skills.rs +++ b/crates/ui/src/windows/skills.rs @@ -24,8 +24,8 @@ use luminol_components::UiExt; +#[derive(Default)] pub struct Window { - skills: Vec, selected_skill_name: Option, previous_skill: Option, @@ -33,14 +33,8 @@ pub struct Window { } impl Window { - pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { - let skills = update_state.data.skills(); - Self { - skills: skills.data.clone(), - selected_skill_name: None, - previous_skill: None, - view: luminol_components::DatabaseView::new(), - } + pub fn new() -> Self { + Default::default() } } @@ -67,6 +61,14 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { + let mut skills = update_state.data.skills(); + let animations = update_state.data.animations(); + let common_events = update_state.data.common_events(); + let system = update_state.data.system(); + let states = update_state.data.states(); + + let mut modified = false; + self.selected_skill_name = None; let response = egui::Window::new(self.name()) @@ -78,36 +80,33 @@ impl luminol_core::Window for Window { ui, update_state, "Skills", - &mut self.skills, + &mut skills.data, |skill| format!("{:0>4}: {}", skill.id + 1, skill.name), - |ui, skills, id, update_state| { + |ui, skills, id| { let skill = &mut skills[id]; self.selected_skill_name = Some(skill.name.clone()); - let animations = update_state.data.animations(); - let common_events = update_state.data.common_events(); - let system = update_state.data.system(); - let states = update_state.data.states(); - ui.with_padded_stripe(false, |ui| { - ui.add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut skill.name) - .desired_width(f32::INFINITY), - )) - .changed(); - - ui.add(luminol_components::Field::new( - "Description", - egui::TextEdit::multiline(&mut skill.description) - .desired_width(f32::INFINITY), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut skill.name) + .desired_width(f32::INFINITY), + )) + .changed(); + + modified |= ui + .add(luminol_components::Field::new( + "Description", + egui::TextEdit::multiline(&mut skill.description) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Scope", luminol_components::EnumComboBox::new( @@ -117,7 +116,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Occasion", luminol_components::EnumComboBox::new( @@ -131,7 +130,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "User Animation", luminol_components::OptionalIdComboBox::new( @@ -149,7 +148,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Target Animation", luminol_components::OptionalIdComboBox::new( @@ -171,14 +170,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Menu Use SE", egui::Label::new("TODO"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Common Event", luminol_components::OptionalIdComboBox::new( @@ -200,7 +199,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "SP Cost", egui::DragValue::new(&mut skill.sp_cost) @@ -208,7 +207,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Power", egui::DragValue::new(&mut skill.power), @@ -219,14 +218,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "ATK-F", egui::Slider::new(&mut skill.atk_f, 0..=200).suffix("%"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "EVA-F", egui::Slider::new(&mut skill.eva_f, 0..=100).suffix("%"), @@ -237,14 +236,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "STR-F", egui::Slider::new(&mut skill.str_f, 0..=100).suffix("%"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "DEX-F", egui::Slider::new(&mut skill.dex_f, 0..=100).suffix("%"), @@ -255,14 +254,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "AGI-F", egui::Slider::new(&mut skill.agi_f, 0..=100).suffix("%"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "INT-F", egui::Slider::new(&mut skill.int_f, 0..=100).suffix("%"), @@ -273,14 +272,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Hit Rate", egui::Slider::new(&mut skill.hit, 0..=100).suffix("%"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Variance", egui::Slider::new(&mut skill.variance, 0..=100).suffix("%"), @@ -291,14 +290,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "PDEF-F", egui::Slider::new(&mut skill.pdef_f, 0..=100).suffix("%"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "MDEF-F", egui::Slider::new(&mut skill.mdef_f, 0..=100).suffix("%"), @@ -324,7 +323,7 @@ impl luminol_core::Window for Window { if self.previous_skill != Some(skill.id) { selection.clear_search(); } - columns[0] + modified |= columns[0] .add(luminol_components::Field::new("Elements", selection)) .changed(); @@ -345,7 +344,7 @@ impl luminol_core::Window for Window { if self.previous_skill != Some(skill.id) { selection.clear_search(); } - columns[1] + modified |= columns[1] .add(luminol_components::Field::new("State Change", selection)) .changed(); }); @@ -356,6 +355,13 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { + modified = true; + } + + if modified { + update_state.modified.set(true); + skills.modified = true; + } } } diff --git a/crates/ui/src/windows/states.rs b/crates/ui/src/windows/states.rs index d578cb83..08862cdd 100644 --- a/crates/ui/src/windows/states.rs +++ b/crates/ui/src/windows/states.rs @@ -24,8 +24,8 @@ use luminol_components::UiExt; +#[derive(Default)] pub struct Window { - states: Vec, selected_state_name: Option, previous_state: Option, @@ -33,14 +33,8 @@ pub struct Window { } impl Window { - pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { - let states = update_state.data.states(); - Self { - states: states.data.clone(), - selected_state_name: None, - previous_state: None, - view: luminol_components::DatabaseView::new(), - } + pub fn new() -> Self { + Default::default() } } @@ -67,6 +61,12 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { + let mut states = update_state.data.states(); + let animations = update_state.data.animations(); + let system = update_state.data.system(); + + let mut modified = false; + self.selected_state_name = None; let response = egui::Window::new(self.name()) @@ -78,27 +78,25 @@ impl luminol_core::Window for Window { ui, update_state, "States", - &mut self.states, + &mut states.data, |state| format!("{:0>4}: {}", state.id + 1, state.name), - |ui, states, id, update_state| { + |ui, states, id| { let state = &mut states[id]; self.selected_state_name = Some(state.name.clone()); - let animations = update_state.data.animations(); - let system = update_state.data.system(); - ui.with_padded_stripe(false, |ui| { - ui.add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut state.name) - .desired_width(f32::INFINITY), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut state.name) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Animation", luminol_components::OptionalIdComboBox::new( @@ -116,7 +114,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Restriction", luminol_components::EnumComboBox::new( @@ -130,14 +128,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Nonresistance", egui::Checkbox::without_text(&mut state.nonresistance), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Count as 0 HP", egui::Checkbox::without_text(&mut state.zero_hp), @@ -148,21 +146,21 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(3, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Can't Get EXP", egui::Checkbox::without_text(&mut state.cant_get_exp), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Can't Evade", egui::Checkbox::without_text(&mut state.cant_evade), )) .changed(); - columns[2] + modified |= columns[2] .add(luminol_components::Field::new( "Slip Damage", egui::Checkbox::without_text(&mut state.slip_damage), @@ -173,14 +171,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Rating", egui::DragValue::new(&mut state.rating).clamp_range(0..=10), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "EVA", egui::DragValue::new(&mut state.eva), @@ -191,7 +189,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Max HP %", egui::Slider::new(&mut state.maxhp_rate, 0..=200) @@ -199,7 +197,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Max SP %", egui::Slider::new(&mut state.maxsp_rate, 0..=200) @@ -211,14 +209,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "STR %", egui::Slider::new(&mut state.str_rate, 0..=200).suffix("%"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "DEX %", egui::Slider::new(&mut state.dex_rate, 0..=200).suffix("%"), @@ -229,14 +227,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "AGI %", egui::Slider::new(&mut state.agi_rate, 0..=200).suffix("%"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "INT %", egui::Slider::new(&mut state.int_rate, 0..=200).suffix("%"), @@ -247,14 +245,14 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Hit Rate %", egui::Slider::new(&mut state.hit_rate, 0..=200).suffix("%"), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "ATK %", egui::Slider::new(&mut state.atk_rate, 0..=200).suffix("%"), @@ -265,7 +263,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "PDEF %", egui::Slider::new(&mut state.pdef_rate, 0..=200) @@ -273,7 +271,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "MDEF %", egui::Slider::new(&mut state.mdef_rate, 0..=200) @@ -285,7 +283,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Auto Release Probability", egui::Slider::new(&mut state.auto_release_prob, 0..=100) @@ -293,7 +291,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Auto Release Interval", egui::DragValue::new(&mut state.hold_turn) @@ -305,7 +303,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Damage Release Probability", egui::Slider::new(&mut state.shock_release_prob, 0..=100) @@ -313,7 +311,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Battle Only", egui::Checkbox::without_text(&mut state.battle_only), @@ -340,7 +338,7 @@ impl luminol_core::Window for Window { if self.previous_state != Some(state.id) { selection.clear_search(); } - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Element Defense", selection, @@ -368,7 +366,7 @@ impl luminol_core::Window for Window { if self.previous_state != Some(state.id) { selection.clear_search(); } - columns[1] + modified |= columns[1] .add(luminol_components::Field::new("State Change", selection)) .changed(); }); @@ -380,6 +378,13 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { + modified = true; + } + + if modified { + update_state.modified.set(true); + states.modified = true; + } } } diff --git a/crates/ui/src/windows/weapons.rs b/crates/ui/src/windows/weapons.rs index 9c2fb819..b75ce381 100644 --- a/crates/ui/src/windows/weapons.rs +++ b/crates/ui/src/windows/weapons.rs @@ -24,8 +24,8 @@ use luminol_components::UiExt; +#[derive(Default)] pub struct Window { - weapons: Vec, selected_weapon_name: Option, previous_weapon: Option, @@ -33,15 +33,8 @@ pub struct Window { } impl Window { - pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { - let weapons = update_state.data.weapons(); - Self { - weapons: weapons.data.clone(), - selected_weapon_name: None, - previous_weapon: None, - - view: luminol_components::DatabaseView::new(), - } + pub fn new() -> Self { + Default::default() } } @@ -68,6 +61,13 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { + let mut weapons = update_state.data.weapons(); + let animations = update_state.data.animations(); + let system = update_state.data.system(); + let states = update_state.data.states(); + + let mut modified = false; + self.selected_weapon_name = None; let response = egui::Window::new(self.name()) @@ -79,35 +79,33 @@ impl luminol_core::Window for Window { ui, update_state, "Weapons", - &mut self.weapons, + &mut weapons.data, |weapon| format!("{:0>4}: {}", weapon.id + 1, weapon.name), - |ui, weapons, id, update_state| { + |ui, weapons, id| { let weapon = &mut weapons[id]; self.selected_weapon_name = Some(weapon.name.clone()); - let animations = update_state.data.animations(); - let system = update_state.data.system(); - let states = update_state.data.states(); - ui.with_padded_stripe(false, |ui| { - ui.add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut weapon.name) - .desired_width(f32::INFINITY), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut weapon.name) + .desired_width(f32::INFINITY), + )) + .changed(); - ui.add(luminol_components::Field::new( - "Description", - egui::TextEdit::multiline(&mut weapon.description) - .desired_width(f32::INFINITY), - )) - .changed(); + modified |= ui + .add(luminol_components::Field::new( + "Description", + egui::TextEdit::multiline(&mut weapon.description) + .desired_width(f32::INFINITY), + )) + .changed(); }); ui.with_padded_stripe(true, |ui| { ui.columns(2, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "User Animation", luminol_components::OptionalIdComboBox::new( @@ -125,7 +123,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "Target Animation", luminol_components::OptionalIdComboBox::new( @@ -147,7 +145,7 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(false, |ui| { ui.columns(4, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "Price", egui::DragValue::new(&mut weapon.price) @@ -155,7 +153,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "ATK", egui::DragValue::new(&mut weapon.atk) @@ -163,7 +161,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[2] + modified |= columns[2] .add(luminol_components::Field::new( "PDEF", egui::DragValue::new(&mut weapon.pdef) @@ -171,7 +169,7 @@ impl luminol_core::Window for Window { )) .changed(); - columns[3] + modified |= columns[3] .add(luminol_components::Field::new( "MDEF", egui::DragValue::new(&mut weapon.mdef) @@ -183,28 +181,28 @@ impl luminol_core::Window for Window { ui.with_padded_stripe(true, |ui| { ui.columns(4, |columns| { - columns[0] + modified |= columns[0] .add(luminol_components::Field::new( "STR+", egui::DragValue::new(&mut weapon.str_plus), )) .changed(); - columns[1] + modified |= columns[1] .add(luminol_components::Field::new( "DEX+", egui::DragValue::new(&mut weapon.dex_plus), )) .changed(); - columns[2] + modified |= columns[2] .add(luminol_components::Field::new( "AGI+", egui::DragValue::new(&mut weapon.agi_plus), )) .changed(); - columns[3] + modified |= columns[3] .add(luminol_components::Field::new( "INT+", egui::DragValue::new(&mut weapon.int_plus), @@ -230,7 +228,7 @@ impl luminol_core::Window for Window { if self.previous_weapon != Some(weapon.id) { selection.clear_search(); } - columns[0] + modified |= columns[0] .add(luminol_components::Field::new("Elements", selection)) .changed(); @@ -251,7 +249,7 @@ impl luminol_core::Window for Window { if self.previous_weapon != Some(weapon.id) { selection.clear_search(); } - columns[1] + modified |= columns[1] .add(luminol_components::Field::new("State Change", selection)) .changed(); }); @@ -262,6 +260,13 @@ impl luminol_core::Window for Window { ) }); - if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) {} + if response.is_some_and(|ir| ir.inner.is_some_and(|ir| ir.inner.modified)) { + modified = true; + } + + if modified { + update_state.modified.set(true); + weapons.modified = true; + } } } diff --git a/src/app/top_bar.rs b/src/app/top_bar.rs index 2aa1a006..f820ecb0 100644 --- a/src/app/top_bar.rs +++ b/src/app/top_bar.rs @@ -211,31 +211,31 @@ impl TopBar { if ui.button("Items").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::items::Window::new(update_state)); + .add_window(luminol_ui::windows::items::Window::new()); } if ui.button("Skills").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::skills::Window::new(update_state)); + .add_window(luminol_ui::windows::skills::Window::new()); } if ui.button("Weapons").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::weapons::Window::new(update_state)); + .add_window(luminol_ui::windows::weapons::Window::new()); } if ui.button("Armor").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::armor::Window::new(update_state)); + .add_window(luminol_ui::windows::armor::Window::new()); } if ui.button("States").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::states::Window::new(update_state)); + .add_window(luminol_ui::windows::states::Window::new()); } ui.separator(); @@ -243,19 +243,19 @@ impl TopBar { if ui.button("Actors").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::actors::Window::new(update_state)); + .add_window(luminol_ui::windows::actors::Window::new()); } if ui.button("Classes").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::classes::Window::new(update_state)); + .add_window(luminol_ui::windows::classes::Window::new()); } if ui.button("Enemies").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::enemies::Window::new(update_state)); + .add_window(luminol_ui::windows::enemies::Window::new()); } ui.add_enabled_ui(false, |ui| { From ccd34fa421b03522cd643d83f0509286245a5e93 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 19:54:16 -0700 Subject: [PATCH 41/67] Only load directory entries on modal opening --- crates/modals/src/event_graphic_picker.rs | 184 ++++++++++++---------- crates/modals/src/sound_picker.rs | 50 ++++-- 2 files changed, 134 insertions(+), 100 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index ca786419..5ad0a4e1 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -28,24 +28,30 @@ use luminol_components::UiExt; use luminol_core::prelude::*; pub struct Modal { - entries: Vec, - filtered_entries: Vec, - search_text: String, - - open: bool, + state: State, id_source: egui::Id, - selected: Selected, - opacity: i32, - hue: i32, - blend_mode: rpg::BlendMode, - tilepicker: Tilepicker, button_viewport: Viewport, button_sprite: Option, } +enum State { + Closed, + Open { + entries: Vec, + filtered_entries: Vec, + search_text: String, + + selected: Selected, + + opacity: i32, + hue: i32, + blend_mode: rpg::BlendMode, + }, +} + #[derive(PartialEq, PartialOrd, Eq, Ord, Clone)] struct Entry { path: camino::Utf8PathBuf, @@ -80,25 +86,6 @@ impl Modal { id_source: egui::Id, ) -> Self { // TODO error handling - let mut entries: Vec<_> = update_state - .filesystem - .read_dir("Graphics/Characters") - .unwrap() - .into_iter() - .map(|m| { - let path = m - .path - .strip_prefix("Graphics/Characters") - .unwrap_or(&m.path) - .with_extension(""); - Entry { - path, - invalid: false, - } - }) - .collect(); - entries.sort_unstable(); - let mut tilepicker = Tilepicker::new( &update_state.graphics, tileset, @@ -119,18 +106,9 @@ impl Modal { .unwrap(); Self { - filtered_entries: entries.clone(), - search_text: String::new(), - entries, - - open: false, + state: State::Closed, id_source, - selected: Selected::None, - opacity: graphic.opacity, - hue: graphic.character_hue, - blend_mode: graphic.blend_type, - tilepicker, button_viewport, @@ -151,7 +129,8 @@ impl luminol_core::Modal for Modal { let desired_size = egui::vec2(64., 96.) + ui.spacing().button_padding * 2.; let (rect, mut response) = ui.allocate_at_least(desired_size, egui::Sense::click()); - let visuals = ui.style().interact_selectable(&response, self.open); + let is_open = matches!(self.state, State::Open { .. }); + let visuals = ui.style().interact_selectable(&response, is_open); let rect = rect.expand(visuals.expansion); ui.painter() .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); @@ -171,8 +150,8 @@ impl luminol_core::Modal for Modal { ui.painter().add(callback); } - if response.clicked() { - self.selected = if let Some(id) = data.tile_id { + if response.clicked() && !is_open { + let selected = if let Some(id) = data.tile_id { Selected::Tile(id) } else if let Some(path) = data.character_name.clone() { let sprite = match Self::load_preview_sprite( @@ -203,11 +182,36 @@ impl luminol_core::Modal for Modal { } else { Selected::None }; - self.blend_mode = data.blend_type; - self.hue = data.character_hue; - self.opacity = data.opacity; - self.open = true; + // FIXME error handling + let mut entries: Vec<_> = update_state + .filesystem + .read_dir("Graphics/Characters") + .unwrap() + .into_iter() + .map(|m| { + let path = m + .path + .strip_prefix("Graphics/Characters") + .unwrap_or(&m.path) + .with_extension(""); + Entry { + path, + invalid: false, + } + }) + .collect(); + entries.sort_unstable(); + + self.state = State::Open { + filtered_entries: entries.clone(), + entries, + search_text: String::new(), + selected, + opacity: data.opacity, + hue: data.character_hue, + blend_mode: data.blend_type, + }; } if self.show_window(update_state, ui.ctx(), data) { response.mark_changed(); @@ -218,7 +222,7 @@ impl luminol_core::Modal for Modal { } fn reset(&mut self) { - self.open = false; + self.state = State::Closed; } } @@ -290,26 +294,40 @@ impl Modal { ctx: &egui::Context, data: &mut rpg::Graphic, ) -> bool { + let mut win_open = true; let mut keep_open = true; let mut needs_save = false; + let State::Open { + entries, + filtered_entries, + search_text, + selected, + opacity, + hue, + blend_mode, + } = &mut self.state + else { + return false; + }; + egui::Window::new("Event Graphic Picker") .resizable(true) - .open(&mut self.open) + .open(&mut win_open) .id(self.id_source.with("window")) .show(ctx, |ui| { egui::SidePanel::left(self.id_source.with("sidebar")).show_inside(ui, |ui| { - let out = egui::TextEdit::singleline(&mut self.search_text) + let out = egui::TextEdit::singleline(search_text) .hint_text("Search 🔎") .show(ui); if out.response.changed() { let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); - self.filtered_entries = self - .entries + *filtered_entries = + entries .iter() .filter(|entry| { matcher - .fuzzy(entry.path.as_str(), &self.search_text, false) + .fuzzy(entry.path.as_str(), search_text, false) .is_some() }) .cloned() @@ -327,21 +345,21 @@ impl Modal { .show_rows( ui, row_height, - self.filtered_entries.len() + 2, + filtered_entries.len() + 2, |ui, mut rows| { if rows.contains(&0) { - let res = ui.selectable_label(matches!(self.selected, Selected::None), "(None)"); - if res.clicked() && !matches!(self.selected, Selected::None) { - self.selected = Selected::None; + let res = ui.selectable_label(matches!(selected, Selected::None), "(None)"); + if res.clicked() && !matches!(selected, Selected::None) { + *selected = Selected::None; } } if rows.contains(&1) { - let checked = matches!(self.selected, Selected::Tile(_)); + let checked = matches!(selected, Selected::Tile(_)); ui.with_stripe(true, |ui| { let res = ui.selectable_label(checked, "(Tileset)"); if res.clicked() && !checked { - self.selected = Selected::Tile(384); + *selected = Selected::Tile(384); } }); } @@ -350,9 +368,9 @@ impl Modal { rows.start = rows.start.saturating_sub(2); rows.end = rows.end.saturating_sub(2); - for (i, Entry { path: entry ,invalid}) in self.filtered_entries[rows.clone()].iter_mut().enumerate() { + for (i, Entry { path: entry ,invalid}) in filtered_entries[rows.clone()].iter_mut().enumerate() { let checked = - matches!(self.selected, Selected::Graphic { ref path, .. } if path == entry); + matches!(selected, Selected::Graphic { ref path, .. } if path == entry); let mut text = egui::RichText::new(entry.as_str()); if *invalid { text = text.color(egui::Color32::LIGHT_RED); @@ -362,7 +380,7 @@ impl Modal { let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); if res.clicked() { - let sprite = match Self::load_preview_sprite(update_state, entry, self.hue, self.opacity) { + let sprite = match Self::load_preview_sprite(update_state, entry, *hue, *opacity) { Ok(sprite) => sprite, Err(e) => { luminol_core::error!(update_state.toasts, e); @@ -370,7 +388,7 @@ impl Modal { return; } }; - self.selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0, sprite }; + *selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0, sprite }; } }); } @@ -381,23 +399,23 @@ impl Modal { egui::TopBottomPanel::top(self.id_source.with("top")).show_inside(ui, |ui| { ui.horizontal(|ui| { ui.label("Opacity"); - if ui.add(egui::Slider::new(&mut self.opacity, 0..=255)).changed() { - self.tilepicker.tiles.display.set_opacity(&update_state.graphics.render_state, self.opacity as f32 / 255., 0); - if let Selected::Graphic { sprite,.. } = &mut self.selected { - sprite.sprite.graphic.set_opacity(&update_state.graphics.render_state, self.opacity); + if ui.add(egui::Slider::new(opacity, 0..=255)).changed() { + self.tilepicker.tiles.display.set_opacity(&update_state.graphics.render_state, *opacity as f32 / 255., 0); + if let Selected::Graphic { sprite,.. } = selected { + sprite.sprite.graphic.set_opacity(&update_state.graphics.render_state, *opacity); } } ui.label("Hue"); - if ui.add(egui::Slider::new(&mut self.hue, 0..=360)).changed() { - self.tilepicker.tiles.display.set_hue(&update_state.graphics.render_state, self.hue as f32 / 360.0, 0); - if let Selected::Graphic { sprite,.. } = &mut self.selected { - sprite.sprite.graphic.set_hue(&update_state.graphics.render_state, self.hue); + if ui.add(egui::Slider::new(hue, 0..=360)).changed() { + self.tilepicker.tiles.display.set_hue(&update_state.graphics.render_state, *hue as f32 / 360.0, 0); + if let Selected::Graphic { sprite,.. } =selected { + sprite.sprite.graphic.set_hue(&update_state.graphics.render_state,*hue); } } }); ui.horizontal(|ui| { ui.label("Blend Mode"); - luminol_components::EnumComboBox::new(self.id_source.with("blend_mode"), &mut self.blend_mode).ui(ui); + luminol_components::EnumComboBox::new(self.id_source.with("blend_mode"), blend_mode).ui(ui); }); }); egui::TopBottomPanel::bottom(self.id_source.with("bottom")).show_inside(ui, |ui| { @@ -407,7 +425,7 @@ impl Modal { egui::CentralPanel::default().show_inside(ui, |ui| { egui::ScrollArea::both().auto_shrink([false,false]).show_viewport(ui, |ui, viewport| { - match &mut self.selected { + match selected { Selected::None => {} Selected::Graphic { direction, pattern, sprite, .. } => { let (canvas_rect, response) = ui.allocate_exact_size( @@ -441,7 +459,7 @@ impl Modal { let ch = sprite.sprite_size.y / 4.; let cw = sprite.sprite_size.x / 4.; - let rect = egui::Rect::from_min_size(egui::pos2(cw ** pattern as f32, ch * (*direction as f32 - 2.) / 2.), egui::vec2(cw, ch)).translate(canvas_rect.min.to_vec2()); + let rect = egui::Rect::from_min_size(egui::pos2(*pattern as f32 * cw, (*direction as f32 - 2.) * ch / 2.), egui::vec2(cw, ch)).translate(canvas_rect.min.to_vec2()); ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); if response.clicked() { @@ -504,13 +522,13 @@ impl Modal { }); if needs_save { - match self.selected { + match selected { Selected::None => { data.tile_id = None; data.character_name = None; } - Selected::Tile(id) => { - data.tile_id = Some(id); + Selected::Tile( id) => { + data.tile_id = Some(*id); data.character_name = None; } Selected::Graphic { @@ -521,18 +539,18 @@ impl Modal { } => { data.tile_id = None; data.character_name = Some(path.clone()); - data.direction = direction; - data.pattern = pattern; + data.direction = *direction; + data.pattern = *pattern; } } - data.blend_type = self.blend_mode; - data.character_hue = self.hue; - data.opacity = self.opacity; + data.blend_type = *blend_mode; + data.character_hue = *hue; + data.opacity = *opacity; self.update_graphic(update_state, data); } - if !keep_open { - self.open = false; + if !(win_open && keep_open) { + self.state = State::Closed; } needs_save diff --git a/crates/modals/src/sound_picker.rs b/crates/modals/src/sound_picker.rs index 57054d20..217cd108 100644 --- a/crates/modals/src/sound_picker.rs +++ b/crates/modals/src/sound_picker.rs @@ -24,18 +24,23 @@ use luminol_core::prelude::*; pub struct Modal { - tab: luminol_components::SoundTab, - open: bool, + state: State, + id_source: egui::Id, + source: Source, +} + +enum State { + Closed, + Open { tab: luminol_components::SoundTab }, } impl Modal { - pub fn new( - filesystem: &impl FileSystem, - source: Source, - selected_track: luminol_data::rpg::AudioFile, - ) -> Self { - let tab = luminol_components::SoundTab::new(filesystem, source, selected_track); - Self { tab, open: false } + pub fn new(source: Source, id_source: impl Into) -> Self { + Self { + state: State::Closed, + id_source: id_source.into(), + source, + } } } @@ -48,8 +53,8 @@ impl luminol_core::Modal for Modal { update_state: &'m mut luminol_core::UpdateState<'_>, ) -> impl egui::Widget + 'm { |ui: &mut egui::Ui| { - let button_text = if let Some(track) = &self.tab.audio_file.name { - format!("Audio/{}/{}", self.tab.source, track) + let button_text = if let Some(track) = &data.name { + format!("Audio/{}/{}", self.source, track) } else { "(None)".to_string() }; @@ -57,7 +62,12 @@ impl luminol_core::Modal for Modal { let mut button_response = ui.button(button_text); if button_response.clicked() { - self.open = true; + let tab = luminol_components::SoundTab::new( + update_state.filesystem, + self.source, + data.clone(), + ); + self.state = State::Open { tab }; } if self.show_window(update_state, ui.ctx(), data) { button_response.mark_changed() @@ -68,7 +78,7 @@ impl luminol_core::Modal for Modal { } fn reset(&mut self) { - self.open = false; + self.state = State::Closed; } } @@ -79,24 +89,30 @@ impl Modal { ctx: &egui::Context, data: &mut luminol_data::rpg::AudioFile, ) -> bool { - let mut win_open = self.open; + let mut win_open = true; let mut keep_open = true; let mut needs_save = false; + let State::Open { tab } = &mut self.state else { + return false; + }; + egui::Window::new("Graphic Picker") .open(&mut win_open) .show(ctx, |ui| { - self.tab.ui(ui, update_state); + tab.ui(ui, update_state); ui.separator(); luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); }); if needs_save { - *data = self.tab.audio_file.clone(); + *data = tab.audio_file.clone(); } - self.open = win_open && keep_open; + if !(win_open && keep_open) { + self.state = State::Closed; + } needs_save } } From 765cb6cdee9bfa954ef56f73ad281464a500695b Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 19:59:01 -0700 Subject: [PATCH 42/67] Pad out top bar of event editor & graphic picker --- crates/modals/src/event_graphic_picker.rs | 2 ++ crates/ui/src/windows/event_edit.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 5ad0a4e1..0af22d4f 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -397,6 +397,7 @@ impl Modal { }); egui::TopBottomPanel::top(self.id_source.with("top")).show_inside(ui, |ui| { + ui.add_space(1.0); // pad out the top ui.horizontal(|ui| { ui.label("Opacity"); if ui.add(egui::Slider::new(opacity, 0..=255)).changed() { @@ -417,6 +418,7 @@ impl Modal { ui.label("Blend Mode"); luminol_components::EnumComboBox::new(self.id_source.with("blend_mode"), blend_mode).ui(ui); }); + ui.add_space(1.0); // pad out the bottom }); egui::TopBottomPanel::bottom(self.id_source.with("bottom")).show_inside(ui, |ui| { ui.add_space(ui.style().spacing.item_spacing.y); diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index ae9c1ac2..ab8a2ba0 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -95,6 +95,7 @@ impl luminol_core::Window for Window { let id_source = self.id(); let previous_page = self.selected_page; egui::TopBottomPanel::top(id_source.with("top_panel")).show_inside(ui, |ui| { + ui.add_space(1.0); // pad the top of the window ui.horizontal(|ui| { ui.label("Name: "); ui.text_edit_singleline(&mut self.event.name); @@ -124,6 +125,7 @@ impl luminol_core::Window for Window { self.event.pages[self.selected_page] = rpg::EventPage::default(); } }); + ui.add_space(1.0); // pad the bottom of the window }); let page = &mut self.event.pages[self.selected_page]; From b0160a4055d5fdcd28c1356f91b36891a3c5f361 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 20:03:50 -0700 Subject: [PATCH 43/67] Use cross justify to expand the trigger window --- crates/components/src/lib.rs | 12 +++++++----- crates/ui/src/windows/event_edit.rs | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/components/src/lib.rs b/crates/components/src/lib.rs index 02a6384b..01730abc 100644 --- a/crates/components/src/lib.rs +++ b/crates/components/src/lib.rs @@ -98,12 +98,14 @@ impl<'e, T: ToString + PartialEq + strum::IntoEnumIterator> egui::Widget for Enu let mut changed = false; let mut response = ui .vertical(|ui| { - for variant in T::iter() { - let text = variant.to_string(); - if ui.radio_value(self.current_value, variant, text).changed() { - changed = true; + ui.with_cross_justify(|ui| { + for variant in T::iter() { + let text = variant.to_string(); + if ui.radio_value(self.current_value, variant, text).changed() { + changed = true; + } } - } + }); }) .response; if changed { diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index ab8a2ba0..1e4067d6 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -183,7 +183,7 @@ impl luminol_core::Window for Window { ); ui.label("is ON"); // ensure we expand to fit the side panel - ui.add_space(ui.available_width()); + ui.add_space(ui.available_width()); // cross justify doesn't seem to be able to replace this? }); }); @@ -257,7 +257,7 @@ impl luminol_core::Window for Window { egui::TopBottomPanel::bottom(id_source.with("bottom_panel")).show_inside( ui, |ui| { - ui.add_space(ui.style().spacing.item_spacing.y); + ui.add_space(1.0); // it still looks a little squished, even with this. how can we fix this? luminol_components::close_options_ui(ui, open, &mut needs_save) }, ); From 317a316521b95cf403fad9347449757076fac1af Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 20:06:18 -0700 Subject: [PATCH 44/67] Fmt --- crates/modals/src/event_graphic_picker.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 0af22d4f..5aba7331 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -322,8 +322,7 @@ impl Modal { .show(ui); if out.response.changed() { let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); - *filtered_entries = - entries + *filtered_entries = entries .iter() .filter(|entry| { matcher @@ -529,7 +528,7 @@ impl Modal { data.tile_id = None; data.character_name = None; } - Selected::Tile( id) => { + Selected::Tile(id) => { data.tile_id = Some(*id); data.character_name = None; } From e398522d74b158bcf2b1098e5af0fb0e913d3ed8 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 20:23:06 -0700 Subject: [PATCH 45/67] mem::take data from update state so we can use it in database view --- crates/components/src/database_view.rs | 6 +++--- crates/ui/src/windows/actors.rs | 22 ++++++++++++++----- crates/ui/src/windows/armor.rs | 15 +++++++++---- crates/ui/src/windows/classes.rs | 24 +++++++++++++++------ crates/ui/src/windows/enemies.rs | 30 ++++++++++++++++++-------- crates/ui/src/windows/items.rs | 21 ++++++++++++------ crates/ui/src/windows/skills.rs | 21 ++++++++++++------ crates/ui/src/windows/states.rs | 15 +++++++++---- crates/ui/src/windows/weapons.rs | 18 +++++++++++----- 9 files changed, 123 insertions(+), 49 deletions(-) diff --git a/crates/components/src/database_view.rs b/crates/components/src/database_view.rs index 6ebad57c..c3832dc3 100644 --- a/crates/components/src/database_view.rs +++ b/crates/components/src/database_view.rs @@ -48,11 +48,11 @@ impl DatabaseView { pub fn show( &mut self, ui: &mut egui::Ui, - update_state: &luminol_core::UpdateState<'_>, + update_state: &mut luminol_core::UpdateState<'_>, label: impl Into, vec: &mut Vec, formatter: impl Fn(&T) -> String, - inner: impl FnOnce(&mut egui::Ui, &mut Vec, usize) -> R, + inner: impl FnOnce(&mut egui::Ui, &mut Vec, usize, &mut luminol_core::UpdateState<'_>) -> R, ) -> egui::InnerResponse> where T: luminol_data::rpg::DatabaseEntry, @@ -262,7 +262,7 @@ impl DatabaseView { DatabaseViewResponse { inner: (self.selected_id < vec.len()) - .then(|| inner(ui, vec, self.selected_id)), + .then(|| inner(ui, vec, self.selected_id, update_state)), modified, } }) diff --git a/crates/ui/src/windows/actors.rs b/crates/ui/src/windows/actors.rs index dbe35e03..7198d66d 100644 --- a/crates/ui/src/windows/actors.rs +++ b/crates/ui/src/windows/actors.rs @@ -222,10 +222,14 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut actors = update_state.data.actors(); - let mut classes = update_state.data.classes(); - let weapons = update_state.data.weapons(); - let armors = update_state.data.armors(); + // we take data temporarily to avoid borrowing issues + // we could probably avoid this with Rc (Data already uses RefCell) but it'd be annoying to work into the existing code + // using Box might be a good idea as well, that's just a pointer copy rather than a full copy + let data = std::mem::take(update_state.data); + let mut actors = data.actors(); + let mut classes = data.classes(); + let weapons = data.weapons(); + let armors = data.armors(); let mut modified = false; @@ -242,7 +246,7 @@ impl luminol_core::Window for Window { "Actors", &mut actors.data, |actor| format!("{:0>4}: {}", actor.id + 1, actor.name), - |ui, actors, id| { + |ui, actors, id, update_state| { let actor = &mut actors[id]; self.selected_actor_name = Some(actor.name.clone()); @@ -729,5 +733,13 @@ impl luminol_core::Window for Window { update_state.modified.set(true); actors.modified = true; } + + // we have to drop things before we can restore data, because the compiler isn't smart enough to do that for us right now + drop(actors); + drop(classes); + drop(weapons); + drop(armors); + + *update_state.data = data; } } diff --git a/crates/ui/src/windows/armor.rs b/crates/ui/src/windows/armor.rs index e1d0eb50..d4febb8f 100644 --- a/crates/ui/src/windows/armor.rs +++ b/crates/ui/src/windows/armor.rs @@ -61,9 +61,10 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut armors = update_state.data.armors(); - let system = update_state.data.system(); - let states = update_state.data.states(); + let data = std::mem::take(update_state.data); // take data to avoid borrow checker issues + let mut armors = data.armors(); + let system = data.system(); + let states = data.states(); let mut modified = false; @@ -80,7 +81,7 @@ impl luminol_core::Window for Window { "Armor", &mut armors.data, |armor| format!("{:0>4}: {}", armor.id + 1, armor.name), - |ui, armors, id| { + |ui, armors, id, update_state| { let armor = &mut armors[id]; self.selected_armor_name = Some(armor.name.clone()); @@ -260,5 +261,11 @@ impl luminol_core::Window for Window { update_state.modified.set(true); armors.modified = true; } + + drop(armors); + drop(system); + drop(states); + + *update_state.data = data; // restore data } } diff --git a/crates/ui/src/windows/classes.rs b/crates/ui/src/windows/classes.rs index 835b575a..05360761 100644 --- a/crates/ui/src/windows/classes.rs +++ b/crates/ui/src/windows/classes.rs @@ -121,12 +121,13 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut classes = update_state.data.classes(); - let system = update_state.data.system(); - let states = update_state.data.states(); - let skills = update_state.data.skills(); - let weapons = update_state.data.weapons(); - let armors = update_state.data.armors(); + let data = std::mem::take(update_state.data); // take data to avoid borrow checker issues + let mut classes = data.classes(); + let system = data.system(); + let states = data.states(); + let skills = data.skills(); + let weapons = data.weapons(); + let armors = data.armors(); let mut modified = false; @@ -143,7 +144,7 @@ impl luminol_core::Window for Window { "Classes", &mut classes.data, |class| format!("{:0>3}: {}", class.id + 1, class.name), - |ui, classes, id| { + |ui, classes, id, update_state| { let class = &mut classes[id]; self.selected_class_name = Some(class.name.clone()); @@ -306,5 +307,14 @@ impl luminol_core::Window for Window { update_state.modified.set(true); classes.modified = true; } + + drop(classes); + drop(system); + drop(states); + drop(skills); + drop(weapons); + drop(armors); + + *update_state.data = data; // restore data } } diff --git a/crates/ui/src/windows/enemies.rs b/crates/ui/src/windows/enemies.rs index 08e0c9e4..c8091b15 100644 --- a/crates/ui/src/windows/enemies.rs +++ b/crates/ui/src/windows/enemies.rs @@ -249,14 +249,15 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut enemies = update_state.data.enemies(); - let animations = update_state.data.animations(); - let system = update_state.data.system(); - let states = update_state.data.states(); - let skills = update_state.data.skills(); - let items = update_state.data.items(); - let weapons = update_state.data.weapons(); - let armors = update_state.data.armors(); + let data = std::mem::take(update_state.data); // take data to avoid borrow checker issues + let mut enemies = data.enemies(); + let animations = data.animations(); + let system = data.system(); + let states = data.states(); + let skills = data.skills(); + let items = data.items(); + let weapons = data.weapons(); + let armors = data.armors(); let mut modified = false; @@ -273,7 +274,7 @@ impl luminol_core::Window for Window { "Enemies", &mut enemies.data, |enemy| format!("{:0>4}: {}", enemy.id + 1, enemy.name), - |ui, enemies, id| { + |ui, enemies, id, update_state| { let enemy = &mut enemies[id]; self.selected_enemy_name = Some(enemy.name.clone()); @@ -643,5 +644,16 @@ impl luminol_core::Window for Window { update_state.modified.set(true); enemies.modified = true; } + + drop(enemies); + drop(animations); + drop(system); + drop(states); + drop(skills); + drop(items); + drop(weapons); + drop(armors); + + *update_state.data = data; // restore data } } diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index a1c3981b..e7d988de 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -70,11 +70,12 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut items = update_state.data.items(); - let animations = update_state.data.animations(); - let common_events = update_state.data.common_events(); - let system = update_state.data.system(); - let states = update_state.data.states(); + let data = std::mem::take(update_state.data); // take data to avoid borrow checker issues + let mut items = data.items(); + let animations = data.animations(); + let common_events = data.common_events(); + let system = data.system(); + let states = data.states(); let mut modified = false; @@ -91,7 +92,7 @@ impl luminol_core::Window for Window { "Items", &mut items.data, |item| format!("{:0>4}: {}", item.id + 1, item.name), - |ui, items, id| { + |ui, items, id, update_state| { let item = &mut items[id]; self.selected_item_name = Some(item.name.clone()); @@ -392,5 +393,13 @@ impl luminol_core::Window for Window { update_state.modified.set(true); items.modified = true; } + + drop(items); + drop(animations); + drop(common_events); + drop(system); + drop(states); + + *update_state.data = data; // restore data } } diff --git a/crates/ui/src/windows/skills.rs b/crates/ui/src/windows/skills.rs index 0d194751..d4c22727 100644 --- a/crates/ui/src/windows/skills.rs +++ b/crates/ui/src/windows/skills.rs @@ -61,11 +61,12 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut skills = update_state.data.skills(); - let animations = update_state.data.animations(); - let common_events = update_state.data.common_events(); - let system = update_state.data.system(); - let states = update_state.data.states(); + let data = std::mem::take(update_state.data); // take data to avoid borrow checker issues + let mut skills = data.skills(); + let animations = data.animations(); + let common_events = data.common_events(); + let system = data.system(); + let states = data.states(); let mut modified = false; @@ -82,7 +83,7 @@ impl luminol_core::Window for Window { "Skills", &mut skills.data, |skill| format!("{:0>4}: {}", skill.id + 1, skill.name), - |ui, skills, id| { + |ui, skills, id, update_state| { let skill = &mut skills[id]; self.selected_skill_name = Some(skill.name.clone()); @@ -363,5 +364,13 @@ impl luminol_core::Window for Window { update_state.modified.set(true); skills.modified = true; } + + drop(skills); + drop(animations); + drop(common_events); + drop(system); + drop(states); + + *update_state.data = data; // restore data } } diff --git a/crates/ui/src/windows/states.rs b/crates/ui/src/windows/states.rs index 08862cdd..6794f0c8 100644 --- a/crates/ui/src/windows/states.rs +++ b/crates/ui/src/windows/states.rs @@ -61,9 +61,10 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut states = update_state.data.states(); - let animations = update_state.data.animations(); - let system = update_state.data.system(); + let data = std::mem::take(update_state.data); // take data to avoid borrow checker issues + let mut states = data.states(); + let animations = data.animations(); + let system = data.system(); let mut modified = false; @@ -80,7 +81,7 @@ impl luminol_core::Window for Window { "States", &mut states.data, |state| format!("{:0>4}: {}", state.id + 1, state.name), - |ui, states, id| { + |ui, states, id, update_state| { let state = &mut states[id]; self.selected_state_name = Some(state.name.clone()); @@ -386,5 +387,11 @@ impl luminol_core::Window for Window { update_state.modified.set(true); states.modified = true; } + + drop(states); + drop(animations); + drop(system); + + *update_state.data = data; // restore data } } diff --git a/crates/ui/src/windows/weapons.rs b/crates/ui/src/windows/weapons.rs index b75ce381..81ba357a 100644 --- a/crates/ui/src/windows/weapons.rs +++ b/crates/ui/src/windows/weapons.rs @@ -61,10 +61,11 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut weapons = update_state.data.weapons(); - let animations = update_state.data.animations(); - let system = update_state.data.system(); - let states = update_state.data.states(); + let data = std::mem::take(update_state.data); // take data to avoid borrow checker issues + let mut weapons = data.weapons(); + let animations = data.animations(); + let system = data.system(); + let states = data.states(); let mut modified = false; @@ -81,7 +82,7 @@ impl luminol_core::Window for Window { "Weapons", &mut weapons.data, |weapon| format!("{:0>4}: {}", weapon.id + 1, weapon.name), - |ui, weapons, id| { + |ui, weapons, id, update_state| { let weapon = &mut weapons[id]; self.selected_weapon_name = Some(weapon.name.clone()); @@ -268,5 +269,12 @@ impl luminol_core::Window for Window { update_state.modified.set(true); weapons.modified = true; } + + drop(weapons); + drop(animations); + drop(system); + drop(states); + + *update_state.data = data; // restore data } } From e698587c4181f61e8ddefeccead48b0cc479d65d Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 20:27:04 -0700 Subject: [PATCH 46/67] Add menu use se modal to item editor --- crates/ui/src/windows/items.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index e7d988de..9ad1adcf 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -23,18 +23,13 @@ // Program grant you additional permission to convey the resulting work. use luminol_components::UiExt; +use luminol_core::Modal; /// Database - Items management window. -#[derive(Default)] pub struct Window { - // ? Items ? selected_item_name: Option, - // ? Icon Graphic Picker ? - _icon_picker: Option, - - // ? Menu Sound Effect Picker ? - _menu_se_picker: Option, + menu_se_picker: luminol_modals::sound_picker::Modal, previous_item: Option, @@ -43,7 +38,21 @@ pub struct Window { impl Window { pub fn new() -> Self { - Default::default() + Self { + selected_item_name: None, + menu_se_picker: luminol_modals::sound_picker::Modal::new( + luminol_audio::Source::SE, + "Menu Use SE", + ), + previous_item: None, + view: luminol_components::DatabaseView::new(), + } + } +} + +impl Default for Window { + fn default() -> Self { + Self::new() } } @@ -183,7 +192,7 @@ impl luminol_core::Window for Window { modified |= columns[0] .add(luminol_components::Field::new( "Menu Use SE", - egui::Label::new("TODO"), + self.menu_se_picker.button(&mut item.menu_se, update_state), )) .changed(); From d6988a12589d3370ff7930f42c1486371ff01bc4 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 20:28:48 -0700 Subject: [PATCH 47/67] Use get_mut instead of borrow() in Data::save() --- crates/core/src/data_cache.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/core/src/data_cache.rs b/crates/core/src/data_cache.rs index 098b8046..03eb2fcc 100644 --- a/crates/core/src/data_cache.rs +++ b/crates/core/src/data_cache.rs @@ -155,7 +155,7 @@ macro_rules! from_defaults { macro_rules! save { ($fs:ident, $type:ident, $field:ident) => {{ - let borrowed = $field.borrow(); + let borrowed = $field.get_mut(); if borrowed.modified { write_nil_padded(&borrowed.data, $fs, format!("{}.rxdata", stringify!($type))) .wrap_err_with(|| format!("While saving {}.rxdata", stringify!($type)))?; @@ -332,7 +332,7 @@ impl Data { modified |= save!(filesystem, Weapons, weapons); { - let map_infos = map_infos.borrow(); + let map_infos = map_infos.get_mut(); if map_infos.modified { modified = true; write_data(&map_infos.data, filesystem, "MapInfos.rxdata") @@ -341,7 +341,7 @@ impl Data { } { - let scripts = scripts.borrow(); + let scripts = scripts.get_mut(); if scripts.modified { modified = true; write_data( @@ -353,7 +353,7 @@ impl Data { } { - let maps = maps.borrow(); + let maps = maps.get_mut(); maps.iter().try_for_each(|(id, map)| { if map.modified { modified = true; From d88270eaa94a835407540c190f08dfe32414fc7e Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Sun, 30 Jun 2024 20:34:38 -0700 Subject: [PATCH 48/67] Clean up sound picker modal a little --- crates/modals/src/sound_picker.rs | 14 ++++++++++---- crates/ui/src/windows/items.rs | 5 ++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/modals/src/sound_picker.rs b/crates/modals/src/sound_picker.rs index 217cd108..ffec79ae 100644 --- a/crates/modals/src/sound_picker.rs +++ b/crates/modals/src/sound_picker.rs @@ -97,13 +97,19 @@ impl Modal { return false; }; - egui::Window::new("Graphic Picker") + egui::Window::new("Sound Picker") .open(&mut win_open) + .id(self.id_source.with("window")) .show(ctx, |ui| { - tab.ui(ui, update_state); - ui.separator(); + egui::TopBottomPanel::bottom(self.id_source.with("bottom_panel")).show_inside( + ui, + |ui| { + ui.add_space(1.0); + luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + }, + ); - luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + tab.ui(ui, update_state); }); if needs_save { diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index 9ad1adcf..c34f18a0 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -42,7 +42,7 @@ impl Window { selected_item_name: None, menu_se_picker: luminol_modals::sound_picker::Modal::new( luminol_audio::Source::SE, - "Menu Use SE", + "item_menu_se_picker", ), previous_item: None, view: luminol_components::DatabaseView::new(), @@ -195,6 +195,9 @@ impl luminol_core::Window for Window { self.menu_se_picker.button(&mut item.menu_se, update_state), )) .changed(); + if self.previous_item != Some(item.id) { + self.menu_se_picker.reset(); + } modified |= columns[1] .add(luminol_components::Field::new( From 6603a1301dfd5ccacf5ec9cb91f7c27619e5bd3d Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 04:13:29 -0700 Subject: [PATCH 49/67] Only load tilepicker when selecting tiles --- crates/graphics/src/loaders/atlas.rs | 8 ++ crates/modals/src/event_graphic_picker.rs | 109 ++++++++++++------ .../mod.rs} | 42 ++++++- crates/ui/src/tabs/map/mod.rs | 4 +- crates/ui/src/tabs/map/util.rs | 3 +- crates/ui/src/windows/event_edit.rs | 4 +- 6 files changed, 126 insertions(+), 44 deletions(-) rename crates/modals/src/{graphic_picker.rs => graphic_picker/mod.rs} (67%) diff --git a/crates/graphics/src/loaders/atlas.rs b/crates/graphics/src/loaders/atlas.rs index 7507b1b6..d517f813 100644 --- a/crates/graphics/src/loaders/atlas.rs +++ b/crates/graphics/src/loaders/atlas.rs @@ -48,6 +48,14 @@ impl Loader { .clone()) } + pub fn get_atlas(&self, id: usize) -> Option { + self.atlases.get(&id).map(|atlas| atlas.clone()) + } + + pub fn get_expect(&self, id: usize) -> Atlas { + self.atlases.get(&id).expect("Atlas not loaded!").clone() + } + pub fn clear(&self) { self.atlases.clear() } diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 5aba7331..4b7f50ac 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -31,7 +31,7 @@ pub struct Modal { state: State, id_source: egui::Id, - tilepicker: Tilepicker, + tileset_id: usize, button_viewport: Viewport, button_sprite: Option, @@ -68,7 +68,10 @@ struct PreviewSprite { enum Selected { #[default] None, - Tile(usize), + Tile { + tile_id: usize, + tilepicker: Tilepicker, + }, Graphic { path: camino::Utf8PathBuf, sprite: PreviewSprite, @@ -82,18 +85,10 @@ impl Modal { pub fn new( update_state: &UpdateState<'_>, graphic: &rpg::Graphic, - tileset: &rpg::Tileset, + tileset_id: usize, id_source: egui::Id, ) -> Self { - // TODO error handling - let mut tilepicker = Tilepicker::new( - &update_state.graphics, - tileset, - update_state.filesystem, - true, - ) - .unwrap(); - tilepicker.tiles.auto_opacity = false; + let atlas = update_state.graphics.atlas_loader.get_expect(tileset_id); // atlas should be loaded by this point let button_viewport = Viewport::new(&update_state.graphics, Default::default()); let button_sprite = Event::new_standalone( @@ -101,7 +96,7 @@ impl Modal { update_state.filesystem, &button_viewport, graphic, - &tilepicker.atlas, + &atlas, ) .unwrap(); @@ -109,7 +104,7 @@ impl Modal { state: State::Closed, id_source, - tilepicker, + tileset_id, button_viewport, button_sprite, @@ -151,8 +146,13 @@ impl luminol_core::Modal for Modal { } if response.clicked() && !is_open { - let selected = if let Some(id) = data.tile_id { - Selected::Tile(id) + let selected = if let Some(tile_id) = data.tile_id { + let tilepicker = Self::load_tilepicker(update_state, self.tileset_id).unwrap(); // TODO handle + + Selected::Tile { + tile_id, + tilepicker, + } } else if let Some(path) = data.character_name.clone() { let sprite = match Self::load_preview_sprite( update_state, @@ -228,16 +228,39 @@ impl luminol_core::Modal for Modal { impl Modal { pub fn update_graphic(&mut self, update_state: &UpdateState<'_>, graphic: &rpg::Graphic) { + let atlas = update_state + .graphics + .atlas_loader + .get_expect(self.tileset_id); // atlas should be loaded by this point + self.button_sprite = Event::new_standalone( &update_state.graphics, update_state.filesystem, &self.button_viewport, graphic, - &self.tilepicker.atlas, + &atlas, ) .unwrap(); } + fn load_tilepicker( + update_state: &UpdateState<'_>, + tileset_id: usize, + ) -> color_eyre::Result { + let tilesets = update_state.data.tilesets(); + let tileset = &tilesets.data[tileset_id]; + + let mut tilepicker = Tilepicker::new( + &update_state.graphics, + tileset, + update_state.filesystem, + true, + )?; + tilepicker.tiles.auto_opacity = false; + + Ok(tilepicker) + } + fn load_preview_sprite( update_state: &luminol_core::UpdateState<'_>, path: &camino::Utf8Path, @@ -354,11 +377,12 @@ impl Modal { } if rows.contains(&1) { - let checked = matches!(selected, Selected::Tile(_)); + let checked = matches!(selected, Selected::Tile {..}); ui.with_stripe(true, |ui| { let res = ui.selectable_label(checked, "(Tileset)"); if res.clicked() && !checked { - *selected = Selected::Tile(384); + let tilepicker = Self::load_tilepicker(update_state, self.tileset_id).unwrap(); // TODO handle + *selected = Selected::Tile { tile_id: 384, tilepicker }; } }); } @@ -400,16 +424,27 @@ impl Modal { ui.horizontal(|ui| { ui.label("Opacity"); if ui.add(egui::Slider::new(opacity, 0..=255)).changed() { - self.tilepicker.tiles.display.set_opacity(&update_state.graphics.render_state, *opacity as f32 / 255., 0); - if let Selected::Graphic { sprite,.. } = selected { - sprite.sprite.graphic.set_opacity(&update_state.graphics.render_state, *opacity); + match selected { + Selected::Graphic { sprite,.. } => { + sprite.sprite.graphic.set_opacity(&update_state.graphics.render_state, *opacity) + }, + Selected::Tile { tilepicker,.. } => { + tilepicker.tiles.display.set_opacity(&update_state.graphics.render_state, *opacity as f32 / 255., 0) + } + Selected::None => {} } + } ui.label("Hue"); if ui.add(egui::Slider::new(hue, 0..=360)).changed() { - self.tilepicker.tiles.display.set_hue(&update_state.graphics.render_state, *hue as f32 / 360.0, 0); - if let Selected::Graphic { sprite,.. } =selected { - sprite.sprite.graphic.set_hue(&update_state.graphics.render_state,*hue); + match selected { + Selected::Graphic { sprite,.. } => { + sprite.sprite.graphic.set_hue(&update_state.graphics.render_state, *hue) + }, + Selected::Tile { tilepicker,.. } => { + tilepicker.tiles.display.set_hue(&update_state.graphics.render_state, *hue as f32 / 360., 0) + } + Selected::None => {} } } }); @@ -469,9 +504,9 @@ impl Modal { *pattern = pos.x as i32; } } - Selected::Tile(id) => { + Selected::Tile { tile_id, tilepicker } => { let (canvas_rect, response) = ui.allocate_exact_size( - egui::vec2(256., self.tilepicker.atlas.tileset_height as f32), + egui::vec2(256., tilepicker.atlas.tileset_height as f32), egui::Sense::click(), ); @@ -481,40 +516,40 @@ impl Modal { .intersect(viewport.translate(canvas_rect.min.to_vec2())); let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); - self.tilepicker.grid.display.set_pixels_per_point( + tilepicker.grid.display.set_pixels_per_point( &update_state.graphics.render_state, ui.ctx().pixels_per_point(), ); - self.tilepicker.set_position( + tilepicker.set_position( &update_state.graphics.render_state, glam::vec2(-scroll_rect.left(), -scroll_rect.top()), ); - self.tilepicker.viewport.set( + tilepicker.viewport.set( &update_state.graphics.render_state, glam::vec2(scroll_rect.width(), scroll_rect.height()), glam::Vec2::ZERO, glam::Vec2::ONE, ); - self.tilepicker + tilepicker .update_animation(&update_state.graphics.render_state, ui.input(|i| i.time)); - let painter = Painter::new(self.tilepicker.prepare(&update_state.graphics)); + let painter = Painter::new(tilepicker.prepare(&update_state.graphics)); ui.painter() .add(luminol_egui_wgpu::Callback::new_paint_callback( absolute_scroll_rect, painter, )); - let tile_x = (*id - 384) % 8; - let tile_y = (*id - 384) / 8; + let tile_x = (*tile_id - 384) % 8; + let tile_y = (*tile_id - 384) / 8; let rect = egui::Rect::from_min_size(egui::Pos2::new(tile_x as f32, tile_y as f32) * 32., egui::Vec2::splat(32.)).translate(canvas_rect.min.to_vec2()); ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); if response.clicked() { let pos = (response.interact_pointer_pos().unwrap() - response.rect.min) / 32.; - *id = pos.x as usize + pos.y as usize * 8 + 384; + *tile_id = pos.x as usize + pos.y as usize * 8 + 384; } } } @@ -528,8 +563,8 @@ impl Modal { data.tile_id = None; data.character_name = None; } - Selected::Tile(id) => { - data.tile_id = Some(*id); + Selected::Tile { tile_id, .. } => { + data.tile_id = Some(*tile_id); data.character_name = None; } Selected::Graphic { diff --git a/crates/modals/src/graphic_picker.rs b/crates/modals/src/graphic_picker/mod.rs similarity index 67% rename from crates/modals/src/graphic_picker.rs rename to crates/modals/src/graphic_picker/mod.rs index 34f3ed32..fb0ae898 100644 --- a/crates/modals/src/graphic_picker.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -22,7 +22,47 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. -pub struct Modal {} +use luminol_core::prelude::*; + +pub struct Modal { + state: State, + id_source: egui::Id, + + button_viewport: Viewport, + button_sprite: Option, +} + +enum State { + Closed, + Open { + entries: Vec, + filtered_entries: Vec, + + search_text: String, + }, +} + +#[derive(Default)] +enum Selected { + #[default] + None, + Entry { + path: camino::Utf8PathBuf, + sprite: PreviewSprite, + }, +} + +struct PreviewSprite { + sprite: Sprite, + sprite_size: egui::Vec2, + viewport: Viewport, +} + +#[derive(PartialEq, PartialOrd, Eq, Ord, Clone)] +struct Entry { + path: camino::Utf8PathBuf, + invalid: bool, +} impl luminol_core::Modal for Modal { type Data = camino::Utf8PathBuf; diff --git a/crates/ui/src/tabs/map/mod.rs b/crates/ui/src/tabs/map/mod.rs index 24fe5d32..d6439f41 100644 --- a/crates/ui/src/tabs/map/mod.rs +++ b/crates/ui/src/tabs/map/mod.rs @@ -482,7 +482,7 @@ impl luminol_core::Tab for Tab { update_state, event, self.id, - tileset, + map.tileset_id, )); } } @@ -555,7 +555,7 @@ impl luminol_core::Tab for Tab { if response.double_clicked() || (is_focused && ui.input(|i| i.key_pressed(egui::Key::Enter))) { - if let Some(id) = self.add_event(update_state, &mut map, tileset) { + if let Some(id) = self.add_event(update_state, &mut map) { self.push_to_history( update_state, &mut map, diff --git a/crates/ui/src/tabs/map/util.rs b/crates/ui/src/tabs/map/util.rs index 43b98856..70802fcb 100644 --- a/crates/ui/src/tabs/map/util.rs +++ b/crates/ui/src/tabs/map/util.rs @@ -203,7 +203,6 @@ impl super::Tab { &mut self, update_state: &luminol_core::UpdateState<'_>, map: &mut luminol_data::rpg::Map, - tileset: &luminol_data::rpg::Tileset, ) -> Option { let mut first_vacant_id = 1; let mut max_event_id = 0; @@ -244,7 +243,7 @@ impl super::Tab { update_state, event, self.id, - tileset, + map.tileset_id, )); Some(new_event_id) } diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index 1e4067d6..bc84974d 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -44,7 +44,7 @@ impl Window { update_state: &UpdateState<'_>, event: rpg::Event, map_id: usize, - tileset: &rpg::Tileset, + tileset_id: usize, ) -> Self { let id_source = egui::Id::new("luminol_event_edit") .with(event.id) @@ -52,7 +52,7 @@ impl Window { let graphic_modal = event_graphic_picker::Modal::new( update_state, &event.pages[0].graphic, - tileset, + tileset_id, id_source.with("graphic_modal"), ); Self { From 7eb948d5fef99b6e81accbefbadf62e6ca19b3e7 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 04:35:34 -0700 Subject: [PATCH 50/67] work on the graphic picker a bit --- crates/graphics/src/primitives/sprite/mod.rs | 26 +++++++++++++ crates/modals/src/event_graphic_picker.rs | 2 +- crates/modals/src/graphic_picker/mod.rs | 39 +++++++++++++++++++- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/crates/graphics/src/primitives/sprite/mod.rs b/crates/graphics/src/primitives/sprite/mod.rs index 00cd25fb..c770a8b8 100644 --- a/crates/graphics/src/primitives/sprite/mod.rs +++ b/crates/graphics/src/primitives/sprite/mod.rs @@ -75,6 +75,32 @@ impl Sprite { bind_group: Arc::new(bind_group), } } + + // like basic, but with a hue + pub fn basic_hue( + graphics_state: &GraphicsState, + hue: i32, + texture: &Texture, + viewport: &Viewport, + ) -> Self { + let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2()); + let quad = Quad::new(rect, rect); + Self::new( + graphics_state, + quad, + hue, + 255, + luminol_data::BlendMode::Normal, + texture, + viewport, + Transform::unit(graphics_state), + ) + } + + // takes the full size of a texture, has no hue, opacity, or blend mode, and uses the identity transform + pub fn basic(graphics_state: &GraphicsState, texture: &Texture, viewport: &Viewport) -> Self { + Self::basic_hue(graphics_state, 0, texture, viewport) + } } pub struct Prepared { diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 4b7f50ac..18579311 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -98,7 +98,7 @@ impl Modal { graphic, &atlas, ) - .unwrap(); + .unwrap(); // FIXME Self { state: State::Closed, diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index fb0ae898..d0708c1d 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -22,12 +22,18 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. +use color_eyre::eyre::Context; +use egui::Widget; +use luminol_components::UiExt; use luminol_core::prelude::*; pub struct Modal { state: State, id_source: egui::Id, + button_size: egui::Vec2, + directory: camino::Utf8PathBuf, // do we make this &'static Utf8Path? + button_viewport: Viewport, button_sprite: Option, } @@ -37,8 +43,9 @@ enum State { Open { entries: Vec, filtered_entries: Vec, - search_text: String, + + selected: Selected, }, } @@ -64,6 +71,36 @@ struct Entry { invalid: bool, } +impl Modal { + pub fn new( + update_state: &UpdateState<'_>, + directory: camino::Utf8PathBuf, + path: Option<&camino::Utf8Path>, + button_size: egui::Vec2, + id_source: egui::Id, + ) -> Self { + let button_viewport = Viewport::new(&update_state.graphics, Default::default()); + let button_sprite = path.map(|path| { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, &directory, path) + .unwrap(); // FIXME + + Sprite::basic(&update_state.graphics, &texture, &button_viewport) + }); + + Self { + state: State::Closed, + id_source, + button_size, + directory, + button_viewport, + button_sprite, + } + } +} + impl luminol_core::Modal for Modal { type Data = camino::Utf8PathBuf; From 81e81a385d147d16ab664116b20954ac2e89e3c0 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 04:41:59 -0700 Subject: [PATCH 51/67] Make Modal::reset actually useful --- crates/core/src/modal.rs | 3 +-- crates/modals/src/database_modal/mod.rs | 3 ++- crates/modals/src/event_graphic_picker.rs | 7 ++++--- crates/modals/src/graphic_picker/mod.rs | 2 +- crates/modals/src/sound_picker.rs | 3 ++- crates/ui/src/windows/event_edit.rs | 5 ++--- crates/ui/src/windows/items.rs | 3 ++- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/crates/core/src/modal.rs b/crates/core/src/modal.rs index 902037d4..1ac00308 100644 --- a/crates/core/src/modal.rs +++ b/crates/core/src/modal.rs @@ -34,6 +34,5 @@ pub trait Modal: Sized { update_state: &'m mut crate::UpdateState<'_>, ) -> impl egui::Widget + 'm; // woah rpitit (so cool) - // FIXME is this used anywhere? - fn reset(&mut self); + fn reset(&mut self, update_state: &mut crate::UpdateState<'_>, data: &Self::Data); } diff --git a/crates/modals/src/database_modal/mod.rs b/crates/modals/src/database_modal/mod.rs index dac34b78..915886d0 100644 --- a/crates/modals/src/database_modal/mod.rs +++ b/crates/modals/src/database_modal/mod.rs @@ -113,7 +113,8 @@ where } } - fn reset(&mut self) { + fn reset(&mut self, _update_state: &mut luminol_core::UpdateState<'_>, _data: &Self::Data) { + // not much internal state, so we dont need to do much here self.state = State::Closed; } } diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 18579311..af03bc83 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -221,13 +221,14 @@ impl luminol_core::Modal for Modal { } } - fn reset(&mut self) { + fn reset(&mut self, update_state: &mut UpdateState<'_>, data: &Self::Data) { + self.update_graphic(update_state, data); // we need to update the button sprite to prevent desyncs self.state = State::Closed; } } impl Modal { - pub fn update_graphic(&mut self, update_state: &UpdateState<'_>, graphic: &rpg::Graphic) { + fn update_graphic(&mut self, update_state: &UpdateState<'_>, graphic: &rpg::Graphic) { let atlas = update_state .graphics .atlas_loader @@ -240,7 +241,7 @@ impl Modal { graphic, &atlas, ) - .unwrap(); + .unwrap(); // FIXME } fn load_tilepicker( diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index d0708c1d..b5125d9b 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -112,7 +112,7 @@ impl luminol_core::Modal for Modal { |ui: &mut egui::Ui| todo!() } - fn reset(&mut self) { + fn reset(&mut self, update_state: &mut luminol_core::UpdateState<'_>, data: &Self::Data) { todo!() } } diff --git a/crates/modals/src/sound_picker.rs b/crates/modals/src/sound_picker.rs index ffec79ae..b0fbe617 100644 --- a/crates/modals/src/sound_picker.rs +++ b/crates/modals/src/sound_picker.rs @@ -77,7 +77,8 @@ impl luminol_core::Modal for Modal { } } - fn reset(&mut self) { + fn reset(&mut self, update_state: &mut luminol_core::UpdateState<'_>, _data: &Self::Data) { + // we don't need to do much here self.state = State::Closed; } } diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index bc84974d..4be5adde 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -130,9 +130,8 @@ impl luminol_core::Window for Window { let page = &mut self.event.pages[self.selected_page]; if self.selected_page != previous_page { - // we need to update the modal to prevent desyncs - self.graphic_modal - .update_graphic(update_state, &page.graphic); + // reset the modal if we've changed pages + self.graphic_modal.reset(update_state, &page.graphic); } egui::SidePanel::left(id_source.with("side_panel")).show_inside(ui, |ui| { diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index c34f18a0..a05c36ab 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -196,7 +196,8 @@ impl luminol_core::Window for Window { )) .changed(); if self.previous_item != Some(item.id) { - self.menu_se_picker.reset(); + // reset the modal if the item has changed (this is practically a no-op) + self.menu_se_picker.reset(update_state, &item.menu_se); } modified |= columns[1] From 31614ec3f6e39a0d487bb54f9289c14cb60b505c Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 05:08:14 -0700 Subject: [PATCH 52/67] Add graphic picker to item editor --- crates/data/src/rmxp/item.rs | 6 +- crates/modals/src/graphic_picker/mod.rs | 131 ++++++++++++++++++++++-- crates/ui/src/windows/items.rs | 46 ++++++--- src/app/top_bar.rs | 2 +- 4 files changed, 158 insertions(+), 27 deletions(-) diff --git a/crates/data/src/rmxp/item.rs b/crates/data/src/rmxp/item.rs index 5d97cc3d..97568a51 100644 --- a/crates/data/src/rmxp/item.rs +++ b/crates/data/src/rmxp/item.rs @@ -16,7 +16,7 @@ // along with Luminol. If not, see . pub use crate::{ id_alox, id_serde, id_vec_alox, id_vec_serde, optional_id_alox, optional_id_serde, - optional_path_serde, rpg::AudioFile, Path, + optional_path_alox, optional_path_serde, rpg::AudioFile, Path, }; #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] @@ -27,7 +27,9 @@ pub struct Item { #[marshal(with = "id_alox")] pub id: usize, pub name: String, - pub icon_name: String, + #[serde(with = "optional_path_serde")] + #[marshal(with = "optional_path_alox")] + pub icon_name: Option, pub description: String, pub scope: crate::rpg::Scope, pub occasion: crate::rpg::Occasion, diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index b5125d9b..00ba1c14 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -34,8 +34,7 @@ pub struct Modal { button_size: egui::Vec2, directory: camino::Utf8PathBuf, // do we make this &'static Utf8Path? - button_viewport: Viewport, - button_sprite: Option, + button_sprite: Option, } enum State { @@ -59,6 +58,12 @@ enum Selected { }, } +struct ButtonSprite { + sprite: Sprite, + sprite_size: egui::Vec2, + viewport: Viewport, +} + struct PreviewSprite { sprite: Sprite, sprite_size: egui::Vec2, @@ -77,9 +82,8 @@ impl Modal { directory: camino::Utf8PathBuf, path: Option<&camino::Utf8Path>, button_size: egui::Vec2, - id_source: egui::Id, + id_source: impl Into, ) -> Self { - let button_viewport = Viewport::new(&update_state.graphics, Default::default()); let button_sprite = path.map(|path| { let texture = update_state .graphics @@ -87,32 +91,139 @@ impl Modal { .load_now_dir(update_state.filesystem, &directory, path) .unwrap(); // FIXME - Sprite::basic(&update_state.graphics, &texture, &button_viewport) + let button_viewport = Viewport::new(&update_state.graphics, Default::default()); + let sprite = Sprite::basic(&update_state.graphics, &texture, &button_viewport); + ButtonSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport: button_viewport, + } }); Self { state: State::Closed, - id_source, + id_source: id_source.into(), button_size, directory, - button_viewport, button_sprite, } } } impl luminol_core::Modal for Modal { - type Data = camino::Utf8PathBuf; + type Data = Option; fn button<'m>( &'m mut self, data: &'m mut Self::Data, update_state: &'m mut luminol_core::UpdateState<'_>, ) -> impl egui::Widget + 'm { - |ui: &mut egui::Ui| todo!() + |ui: &mut egui::Ui| { + let desired_size = self.button_size + ui.spacing().button_padding * 2.0; + let (rect, mut response) = ui.allocate_at_least(desired_size, egui::Sense::click()); + + let is_open = matches!(self.state, State::Open { .. }); + let visuals = ui.style().interact_selectable(&response, is_open); + let rect = rect.expand(visuals.expansion); + ui.painter() + .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); + + if let Some(ButtonSprite { + sprite, + sprite_size, + viewport, + }) = &mut self.button_sprite + { + let translation = (desired_size - *sprite_size) / 2.; + viewport.set( + &update_state.graphics.render_state, + glam::vec2(desired_size.x, desired_size.y), + glam::vec2(translation.x, translation.y), + glam::Vec2::ONE, + ); + let callback = luminol_egui_wgpu::Callback::new_paint_callback( + response.rect, + Painter::new(sprite.prepare(&update_state.graphics)), + ); + ui.painter().add(callback); + } + + if response.clicked() && !is_open { + let selected = match data.clone() { + Some(path) => todo!(), + None => Selected::None, + }; + + // FIXME error handling + let mut entries: Vec<_> = update_state + .filesystem + .read_dir(&self.directory) + .unwrap() + .into_iter() + .map(|m| { + let path = m + .path + .strip_prefix(&self.directory) + .unwrap_or(&m.path) + .with_extension(""); + Entry { + path, + invalid: false, + } + }) + .collect(); + entries.sort_unstable(); + + self.state = State::Open { + filtered_entries: entries.clone(), + entries, + search_text: String::new(), + selected, + }; + } + if self.show_window(update_state, ui.ctx(), data) { + response.mark_changed(); + } + + response + } } fn reset(&mut self, update_state: &mut luminol_core::UpdateState<'_>, data: &Self::Data) { - todo!() + self.update_graphic(update_state, data); // we need to update the button sprite to prevent desyncs + self.state = State::Closed; + } +} + +impl Modal { + fn update_graphic( + &mut self, + update_state: &UpdateState<'_>, + data: &Option, + ) { + self.button_sprite = data.as_ref().map(|path| { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, &self.directory, path) + .unwrap(); // FIXME + + let button_viewport = Viewport::new(&update_state.graphics, Default::default()); + let sprite = Sprite::basic(&update_state.graphics, &texture, &button_viewport); + ButtonSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport: button_viewport, + } + }); + } + + fn show_window( + &mut self, + update_state: &mut luminol_core::UpdateState<'_>, + ctx: &egui::Context, + data: &mut Option, + ) -> bool { + false } } diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index a05c36ab..55b05760 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -30,6 +30,7 @@ pub struct Window { selected_item_name: Option, menu_se_picker: luminol_modals::sound_picker::Modal, + graphic_picker: luminol_modals::graphic_picker::Modal, previous_item: Option, @@ -37,25 +38,28 @@ pub struct Window { } impl Window { - pub fn new() -> Self { + pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { + let items = update_state.data.items(); + let item = &items.data[0]; Self { selected_item_name: None, menu_se_picker: luminol_modals::sound_picker::Modal::new( luminol_audio::Source::SE, "item_menu_se_picker", ), + graphic_picker: luminol_modals::graphic_picker::Modal::new( + update_state, + camino::Utf8PathBuf::from("Graphics/Icons"), + item.icon_name.as_deref(), + egui::vec2(32., 32.), + "item_icon_picker", + ), previous_item: None, view: luminol_components::DatabaseView::new(), } } } -impl Default for Window { - fn default() -> Self { - Self::new() - } -} - impl luminol_core::Window for Window { fn name(&self) -> String { if let Some(name) = &self.selected_item_name { @@ -106,13 +110,27 @@ impl luminol_core::Window for Window { self.selected_item_name = Some(item.name.clone()); ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut item.name) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.horizontal(|ui| { + modified |= ui + .add(luminol_components::Field::new( + "Icon", + self.graphic_picker + .button(&mut item.icon_name, update_state), + )) + .changed(); + if self.previous_item != Some(item.id) { + // avoid desyncs by resetting the modal if the item has changed + self.graphic_picker.reset(update_state, &item.icon_name); + } + + modified |= ui + .add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut item.name) + .desired_width(f32::INFINITY), + )) + .changed(); + }); modified |= ui .add(luminol_components::Field::new( diff --git a/src/app/top_bar.rs b/src/app/top_bar.rs index f820ecb0..e3a4027d 100644 --- a/src/app/top_bar.rs +++ b/src/app/top_bar.rs @@ -211,7 +211,7 @@ impl TopBar { if ui.button("Items").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::items::Window::new()); + .add_window(luminol_ui::windows::items::Window::new(update_state)); } if ui.button("Skills").clicked() { From cedeba5954ccf2f8f103f0ab215b7c82ec670798 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 05:25:16 -0700 Subject: [PATCH 53/67] Fill out graphic picker more --- crates/modals/src/event_graphic_picker.rs | 10 +- crates/modals/src/graphic_picker/mod.rs | 187 +++++++++++++++++++++- crates/modals/src/sound_picker.rs | 2 +- 3 files changed, 190 insertions(+), 9 deletions(-) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index af03bc83..879ad74d 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -392,10 +392,10 @@ impl Modal { rows.start = rows.start.saturating_sub(2); rows.end = rows.end.saturating_sub(2); - for (i, Entry { path: entry ,invalid}) in filtered_entries[rows.clone()].iter_mut().enumerate() { + for (i, Entry { path ,invalid}) in filtered_entries[rows.clone()].iter_mut().enumerate() { let checked = - matches!(selected, Selected::Graphic { ref path, .. } if path == entry); - let mut text = egui::RichText::new(entry.as_str()); + matches!(selected, Selected::Graphic { path: p, .. } if p == path); + let mut text = egui::RichText::new(path.as_str()); if *invalid { text = text.color(egui::Color32::LIGHT_RED); } @@ -404,7 +404,7 @@ impl Modal { let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); if res.clicked() { - let sprite = match Self::load_preview_sprite(update_state, entry, *hue, *opacity) { + let sprite = match Self::load_preview_sprite(update_state, path, *hue, *opacity) { Ok(sprite) => sprite, Err(e) => { luminol_core::error!(update_state.toasts, e); @@ -412,7 +412,7 @@ impl Modal { return; } }; - *selected = Selected::Graphic { path: entry.clone(), direction: 2, pattern: 0, sprite }; + *selected = Selected::Graphic { path: path.clone(), direction: 2, pattern: 0, sprite }; } }); } diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index 00ba1c14..512be8a6 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -23,7 +23,6 @@ // Program grant you additional permission to convey the resulting work. use color_eyre::eyre::Context; -use egui::Widget; use luminol_components::UiExt; use luminol_core::prelude::*; @@ -150,7 +149,13 @@ impl luminol_core::Modal for Modal { if response.clicked() && !is_open { let selected = match data.clone() { - Some(path) => todo!(), + Some(path) => { + // FIXME error handling + let sprite = + Self::load_preview_sprite(update_state, &self.directory, &path) + .unwrap(); + Selected::Entry { path, sprite } + } None => Selected::None, }; @@ -196,6 +201,40 @@ impl luminol_core::Modal for Modal { } impl Modal { + fn load_preview_sprite( + update_state: &luminol_core::UpdateState<'_>, + directory: &camino::Utf8Path, + path: &camino::Utf8Path, + ) -> color_eyre::Result { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, directory, path) + .wrap_err("While loading a preview sprite")?; + + Ok(Self::create_preview_sprite_from_texture( + update_state, + &texture, + )) + } + + fn create_preview_sprite_from_texture( + update_state: &luminol_core::UpdateState<'_>, + texture: &Texture, + ) -> PreviewSprite { + let viewport = Viewport::new( + &update_state.graphics, + glam::vec2(texture.width() as f32, texture.height() as f32), + ); + + let sprite = Sprite::basic(&update_state.graphics, texture, &viewport); + PreviewSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport, + } + } + fn update_graphic( &mut self, update_state: &UpdateState<'_>, @@ -224,6 +263,148 @@ impl Modal { ctx: &egui::Context, data: &mut Option, ) -> bool { - false + let mut win_open = true; + let mut keep_open = true; + let mut needs_save = false; + + let State::Open { + entries, + filtered_entries, + search_text, + selected, + } = &mut self.state + else { + return false; + }; + + egui::Window::new("Graphic Picker") + .resizable(true) + .open(&mut win_open) + .id(self.id_source.with("window")) + .show(ctx, |ui| { + egui::SidePanel::left(self.id_source.with("sidebar")).show_inside(ui, |ui| { + let out = egui::TextEdit::singleline(search_text) + .hint_text("Search 🔎") + .show(ui); + if out.response.changed() { + let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); + *filtered_entries = entries + .iter() + .filter(|entry| { + matcher + .fuzzy(entry.path.as_str(), search_text, false) + .is_some() + }) + .cloned() + .collect(); + } + + ui.separator(); + + // Get row height. + let row_height = ui.text_style_height(&egui::TextStyle::Body); // i do not trust this + // FIXME scroll to selected on first open + ui.with_cross_justify(|ui| { + egui::ScrollArea::vertical() + .auto_shrink([false, true]) + .show_rows( + ui, + row_height, + filtered_entries.len() + 1, + |ui, mut rows| { + if rows.contains(&0) { + let res = ui.selectable_label( + matches!(selected, Selected::None), + "(None)", + ); + if res.clicked() && !matches!(selected, Selected::None) { + *selected = Selected::None; + } + } + + // subtract 2 to account for (None) + rows.start = rows.start.saturating_sub(1); + rows.end = rows.end.saturating_sub(1); + + for i in filtered_entries[rows.clone()].iter_mut().enumerate() { + let (i, Entry { path, invalid }) = i; + let checked = matches!(selected, Selected::Entry { path: p, .. } if p == path); + let mut text = egui::RichText::new(path.as_str()); + if *invalid { + text = text.color(egui::Color32::LIGHT_RED); + } + let faint = (i + rows.start) % 2 == 1; + ui.with_stripe(faint, |ui| { + let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); + + if res.clicked() { + let sprite = Self::load_preview_sprite(update_state, &self.directory, path).unwrap(); + *selected = Selected::Entry { path:path.clone(), sprite }; + } + }); + } + }, + ); + }); + }); + + egui::TopBottomPanel::bottom(self.id_source.with("bottom")).show_inside(ui, |ui| { + ui.add_space(ui.style().spacing.item_spacing.y); + luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + }); + + egui::CentralPanel::default().show_inside(ui, |ui| { + egui::ScrollArea::both().auto_shrink([false,false]).show_viewport(ui, |ui, viewport| { + match selected { + Selected::None => {} + Selected::Entry { sprite,.. } => { + let (canvas_rect, _) = ui.allocate_exact_size( + sprite.sprite_size, + egui::Sense::focusable_noninteractive(), // FIXME screen reader hints + ); + + let absolute_scroll_rect = ui + .ctx() + .screen_rect() + .intersect(viewport.translate(canvas_rect.min.to_vec2())); + let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); + sprite.sprite.transform.set_position( + &update_state.graphics.render_state, + glam::vec2(-scroll_rect.left(), -scroll_rect.top()), + ); + + sprite.viewport.set( + &update_state.graphics.render_state, + glam::vec2(absolute_scroll_rect.width(), absolute_scroll_rect.height()), + glam::Vec2::ZERO, + glam::Vec2::ONE, + ); + + let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); + ui.painter() + .add(luminol_egui_wgpu::Callback::new_paint_callback( + absolute_scroll_rect, + painter, + )); + } + } + }); + }); + }); + + if needs_save { + match selected { + Selected::None => *data = None, + Selected::Entry { path, .. } => *data = Some(path.clone()), + } + + self.update_graphic(update_state, data); + } + + if !(win_open && keep_open) { + self.state = State::Closed; + } + + needs_save } } diff --git a/crates/modals/src/sound_picker.rs b/crates/modals/src/sound_picker.rs index b0fbe617..49fe0692 100644 --- a/crates/modals/src/sound_picker.rs +++ b/crates/modals/src/sound_picker.rs @@ -77,7 +77,7 @@ impl luminol_core::Modal for Modal { } } - fn reset(&mut self, update_state: &mut luminol_core::UpdateState<'_>, _data: &Self::Data) { + fn reset(&mut self, _: &mut luminol_core::UpdateState<'_>, _data: &Self::Data) { // we don't need to do much here self.state = State::Closed; } From bbc9cb4543eb63b57664be71fc378f51c289c6de Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 05:44:10 -0700 Subject: [PATCH 54/67] Make modals take data by value (might revert this) --- crates/core/src/modal.rs | 11 ++++++++--- crates/modals/src/database_modal/mod.rs | 13 +++++++++---- crates/modals/src/event_graphic_picker.rs | 7 ++++--- crates/modals/src/graphic_picker/mod.rs | 11 ++++++++--- crates/modals/src/sound_picker.rs | 7 ++++--- crates/ui/src/windows/event_edit.rs | 2 +- crates/ui/src/windows/items.rs | 3 ++- 7 files changed, 36 insertions(+), 18 deletions(-) diff --git a/crates/core/src/modal.rs b/crates/core/src/modal.rs index 1ac00308..8336788b 100644 --- a/crates/core/src/modal.rs +++ b/crates/core/src/modal.rs @@ -25,14 +25,19 @@ /// A basic trait describing a modal that edits some value. pub trait Modal: Sized { /// The output type for this modal. - type Data; + type Data<'m> + where + Self: 'm; + type ResetData<'m> + where + Self: 'm; /// Return a widget that displays a button for this modal. fn button<'m>( &'m mut self, - data: &'m mut Self::Data, + data: Self::Data<'m>, update_state: &'m mut crate::UpdateState<'_>, ) -> impl egui::Widget + 'm; // woah rpitit (so cool) - fn reset(&mut self, update_state: &mut crate::UpdateState<'_>, data: &Self::Data); + fn reset(&mut self, update_state: &mut crate::UpdateState<'_>, data: Self::ResetData<'_>); } diff --git a/crates/modals/src/database_modal/mod.rs b/crates/modals/src/database_modal/mod.rs index 915886d0..c940f580 100644 --- a/crates/modals/src/database_modal/mod.rs +++ b/crates/modals/src/database_modal/mod.rs @@ -81,13 +81,14 @@ where impl luminol_core::Modal for Modal where - M: DatabaseModalHandler, + M: DatabaseModalHandler + 'static, { - type Data = usize; + type Data<'m> = &'m mut usize; + type ResetData<'m> = &'m usize; fn button<'m>( &'m mut self, - data: &'m mut Self::Data, + data: Self::Data<'m>, update_state: &'m mut luminol_core::UpdateState<'_>, ) -> impl egui::Widget + 'm { move |ui: &mut egui::Ui| { @@ -113,7 +114,11 @@ where } } - fn reset(&mut self, _update_state: &mut luminol_core::UpdateState<'_>, _data: &Self::Data) { + fn reset( + &mut self, + _update_state: &mut luminol_core::UpdateState<'_>, + _data: Self::ResetData<'_>, + ) { // not much internal state, so we dont need to do much here self.state = State::Closed; } diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index 879ad74d..eebdef32 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -113,11 +113,12 @@ impl Modal { } impl luminol_core::Modal for Modal { - type Data = luminol_data::rpg::Graphic; + type Data<'m> = &'m mut luminol_data::rpg::Graphic; + type ResetData<'m> = &'m luminol_data::rpg::Graphic; fn button<'m>( &'m mut self, - data: &'m mut Self::Data, + data: Self::Data<'m>, update_state: &'m mut UpdateState<'_>, ) -> impl egui::Widget + 'm { move |ui: &mut egui::Ui| { @@ -221,7 +222,7 @@ impl luminol_core::Modal for Modal { } } - fn reset(&mut self, update_state: &mut UpdateState<'_>, data: &Self::Data) { + fn reset(&mut self, update_state: &mut UpdateState<'_>, data: Self::ResetData<'_>) { self.update_graphic(update_state, data); // we need to update the button sprite to prevent desyncs self.state = State::Closed; } diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index 512be8a6..352ead29 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -110,11 +110,12 @@ impl Modal { } impl luminol_core::Modal for Modal { - type Data = Option; + type Data<'m> = &'m mut Option; + type ResetData<'m> = &'m Option; fn button<'m>( &'m mut self, - data: &'m mut Self::Data, + data: Self::Data<'m>, update_state: &'m mut luminol_core::UpdateState<'_>, ) -> impl egui::Widget + 'm { |ui: &mut egui::Ui| { @@ -194,7 +195,11 @@ impl luminol_core::Modal for Modal { } } - fn reset(&mut self, update_state: &mut luminol_core::UpdateState<'_>, data: &Self::Data) { + fn reset( + &mut self, + update_state: &mut luminol_core::UpdateState<'_>, + data: Self::ResetData<'_>, + ) { self.update_graphic(update_state, data); // we need to update the button sprite to prevent desyncs self.state = State::Closed; } diff --git a/crates/modals/src/sound_picker.rs b/crates/modals/src/sound_picker.rs index 49fe0692..6d00a1fc 100644 --- a/crates/modals/src/sound_picker.rs +++ b/crates/modals/src/sound_picker.rs @@ -45,11 +45,12 @@ impl Modal { } impl luminol_core::Modal for Modal { - type Data = luminol_data::rpg::AudioFile; + type Data<'m> = &'m mut luminol_data::rpg::AudioFile; + type ResetData<'m> = &'m luminol_data::rpg::AudioFile; fn button<'m>( &'m mut self, - data: &'m mut Self::Data, + data: Self::Data<'m>, update_state: &'m mut luminol_core::UpdateState<'_>, ) -> impl egui::Widget + 'm { |ui: &mut egui::Ui| { @@ -77,7 +78,7 @@ impl luminol_core::Modal for Modal { } } - fn reset(&mut self, _: &mut luminol_core::UpdateState<'_>, _data: &Self::Data) { + fn reset(&mut self, _: &mut luminol_core::UpdateState<'_>, _data: Self::ResetData<'_>) { // we don't need to do much here self.state = State::Closed; } diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index 4be5adde..63512983 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -131,7 +131,7 @@ impl luminol_core::Window for Window { let page = &mut self.event.pages[self.selected_page]; if self.selected_page != previous_page { // reset the modal if we've changed pages - self.graphic_modal.reset(update_state, &page.graphic); + self.graphic_modal.reset(update_state, &&mut page.graphic); } egui::SidePanel::left(id_source.with("side_panel")).show_inside(ui, |ui| { diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index 55b05760..28f3ad89 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -120,7 +120,8 @@ impl luminol_core::Window for Window { .changed(); if self.previous_item != Some(item.id) { // avoid desyncs by resetting the modal if the item has changed - self.graphic_picker.reset(update_state, &item.icon_name); + self.graphic_picker + .reset(update_state, &&mut item.icon_name); } modified |= ui From 7395dc9ec13217e59d25ad5cc2de5ddb0e02a78e Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 05:45:56 -0700 Subject: [PATCH 55/67] Remove ResetData Was annoying and probably not needed --- crates/core/src/modal.rs | 9 ++------- crates/modals/src/database_modal/mod.rs | 9 ++------- crates/modals/src/event_graphic_picker.rs | 3 +-- crates/modals/src/graphic_picker/mod.rs | 7 +------ crates/modals/src/sound_picker.rs | 3 +-- crates/ui/src/windows/event_edit.rs | 2 +- crates/ui/src/windows/items.rs | 5 ++--- 7 files changed, 10 insertions(+), 28 deletions(-) diff --git a/crates/core/src/modal.rs b/crates/core/src/modal.rs index 8336788b..36944100 100644 --- a/crates/core/src/modal.rs +++ b/crates/core/src/modal.rs @@ -25,12 +25,7 @@ /// A basic trait describing a modal that edits some value. pub trait Modal: Sized { /// The output type for this modal. - type Data<'m> - where - Self: 'm; - type ResetData<'m> - where - Self: 'm; + type Data<'m>; /// Return a widget that displays a button for this modal. fn button<'m>( @@ -39,5 +34,5 @@ pub trait Modal: Sized { update_state: &'m mut crate::UpdateState<'_>, ) -> impl egui::Widget + 'm; // woah rpitit (so cool) - fn reset(&mut self, update_state: &mut crate::UpdateState<'_>, data: Self::ResetData<'_>); + fn reset(&mut self, update_state: &mut crate::UpdateState<'_>, data: Self::Data<'_>); } diff --git a/crates/modals/src/database_modal/mod.rs b/crates/modals/src/database_modal/mod.rs index c940f580..4ed09075 100644 --- a/crates/modals/src/database_modal/mod.rs +++ b/crates/modals/src/database_modal/mod.rs @@ -81,10 +81,9 @@ where impl luminol_core::Modal for Modal where - M: DatabaseModalHandler + 'static, + M: DatabaseModalHandler, { type Data<'m> = &'m mut usize; - type ResetData<'m> = &'m usize; fn button<'m>( &'m mut self, @@ -114,11 +113,7 @@ where } } - fn reset( - &mut self, - _update_state: &mut luminol_core::UpdateState<'_>, - _data: Self::ResetData<'_>, - ) { + fn reset(&mut self, _update_state: &mut luminol_core::UpdateState<'_>, _data: Self::Data<'_>) { // not much internal state, so we dont need to do much here self.state = State::Closed; } diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/event_graphic_picker.rs index eebdef32..3a722280 100644 --- a/crates/modals/src/event_graphic_picker.rs +++ b/crates/modals/src/event_graphic_picker.rs @@ -114,7 +114,6 @@ impl Modal { impl luminol_core::Modal for Modal { type Data<'m> = &'m mut luminol_data::rpg::Graphic; - type ResetData<'m> = &'m luminol_data::rpg::Graphic; fn button<'m>( &'m mut self, @@ -222,7 +221,7 @@ impl luminol_core::Modal for Modal { } } - fn reset(&mut self, update_state: &mut UpdateState<'_>, data: Self::ResetData<'_>) { + fn reset(&mut self, update_state: &mut UpdateState<'_>, data: Self::Data<'_>) { self.update_graphic(update_state, data); // we need to update the button sprite to prevent desyncs self.state = State::Closed; } diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index 352ead29..c9019813 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -111,7 +111,6 @@ impl Modal { impl luminol_core::Modal for Modal { type Data<'m> = &'m mut Option; - type ResetData<'m> = &'m Option; fn button<'m>( &'m mut self, @@ -195,11 +194,7 @@ impl luminol_core::Modal for Modal { } } - fn reset( - &mut self, - update_state: &mut luminol_core::UpdateState<'_>, - data: Self::ResetData<'_>, - ) { + fn reset(&mut self, update_state: &mut luminol_core::UpdateState<'_>, data: Self::Data<'_>) { self.update_graphic(update_state, data); // we need to update the button sprite to prevent desyncs self.state = State::Closed; } diff --git a/crates/modals/src/sound_picker.rs b/crates/modals/src/sound_picker.rs index 6d00a1fc..64e7946f 100644 --- a/crates/modals/src/sound_picker.rs +++ b/crates/modals/src/sound_picker.rs @@ -46,7 +46,6 @@ impl Modal { impl luminol_core::Modal for Modal { type Data<'m> = &'m mut luminol_data::rpg::AudioFile; - type ResetData<'m> = &'m luminol_data::rpg::AudioFile; fn button<'m>( &'m mut self, @@ -78,7 +77,7 @@ impl luminol_core::Modal for Modal { } } - fn reset(&mut self, _: &mut luminol_core::UpdateState<'_>, _data: Self::ResetData<'_>) { + fn reset(&mut self, _: &mut luminol_core::UpdateState<'_>, _data: Self::Data<'_>) { // we don't need to do much here self.state = State::Closed; } diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index 63512983..b74fc43a 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -131,7 +131,7 @@ impl luminol_core::Window for Window { let page = &mut self.event.pages[self.selected_page]; if self.selected_page != previous_page { // reset the modal if we've changed pages - self.graphic_modal.reset(update_state, &&mut page.graphic); + self.graphic_modal.reset(update_state, &mut page.graphic); } egui::SidePanel::left(id_source.with("side_panel")).show_inside(ui, |ui| { diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index 28f3ad89..f2f7ef47 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -120,8 +120,7 @@ impl luminol_core::Window for Window { .changed(); if self.previous_item != Some(item.id) { // avoid desyncs by resetting the modal if the item has changed - self.graphic_picker - .reset(update_state, &&mut item.icon_name); + self.graphic_picker.reset(update_state, &mut item.icon_name); } modified |= ui @@ -216,7 +215,7 @@ impl luminol_core::Window for Window { .changed(); if self.previous_item != Some(item.id) { // reset the modal if the item has changed (this is practically a no-op) - self.menu_se_picker.reset(update_state, &item.menu_se); + self.menu_se_picker.reset(update_state, &mut item.menu_se); } modified |= columns[1] From 85508a471a69abbce5568c6a104e2c282669f6ec Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 05:53:05 -0700 Subject: [PATCH 56/67] Move current graphic picker into "basic" --- crates/modals/src/graphic_picker/basic.rs | 410 ++++++++++++++++++++++ crates/modals/src/graphic_picker/mod.rs | 389 +------------------- crates/ui/src/windows/items.rs | 14 +- 3 files changed, 419 insertions(+), 394 deletions(-) create mode 100644 crates/modals/src/graphic_picker/basic.rs diff --git a/crates/modals/src/graphic_picker/basic.rs b/crates/modals/src/graphic_picker/basic.rs new file mode 100644 index 00000000..c9019813 --- /dev/null +++ b/crates/modals/src/graphic_picker/basic.rs @@ -0,0 +1,410 @@ +// Copyright (C) 2023 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 color_eyre::eyre::Context; +use luminol_components::UiExt; +use luminol_core::prelude::*; + +pub struct Modal { + state: State, + id_source: egui::Id, + + button_size: egui::Vec2, + directory: camino::Utf8PathBuf, // do we make this &'static Utf8Path? + + button_sprite: Option, +} + +enum State { + Closed, + Open { + entries: Vec, + filtered_entries: Vec, + search_text: String, + + selected: Selected, + }, +} + +#[derive(Default)] +enum Selected { + #[default] + None, + Entry { + path: camino::Utf8PathBuf, + sprite: PreviewSprite, + }, +} + +struct ButtonSprite { + sprite: Sprite, + sprite_size: egui::Vec2, + viewport: Viewport, +} + +struct PreviewSprite { + sprite: Sprite, + sprite_size: egui::Vec2, + viewport: Viewport, +} + +#[derive(PartialEq, PartialOrd, Eq, Ord, Clone)] +struct Entry { + path: camino::Utf8PathBuf, + invalid: bool, +} + +impl Modal { + pub fn new( + update_state: &UpdateState<'_>, + directory: camino::Utf8PathBuf, + path: Option<&camino::Utf8Path>, + button_size: egui::Vec2, + id_source: impl Into, + ) -> Self { + let button_sprite = path.map(|path| { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, &directory, path) + .unwrap(); // FIXME + + let button_viewport = Viewport::new(&update_state.graphics, Default::default()); + let sprite = Sprite::basic(&update_state.graphics, &texture, &button_viewport); + ButtonSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport: button_viewport, + } + }); + + Self { + state: State::Closed, + id_source: id_source.into(), + button_size, + directory, + button_sprite, + } + } +} + +impl luminol_core::Modal for Modal { + type Data<'m> = &'m mut Option; + + fn button<'m>( + &'m mut self, + data: Self::Data<'m>, + update_state: &'m mut luminol_core::UpdateState<'_>, + ) -> impl egui::Widget + 'm { + |ui: &mut egui::Ui| { + let desired_size = self.button_size + ui.spacing().button_padding * 2.0; + let (rect, mut response) = ui.allocate_at_least(desired_size, egui::Sense::click()); + + let is_open = matches!(self.state, State::Open { .. }); + let visuals = ui.style().interact_selectable(&response, is_open); + let rect = rect.expand(visuals.expansion); + ui.painter() + .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); + + if let Some(ButtonSprite { + sprite, + sprite_size, + viewport, + }) = &mut self.button_sprite + { + let translation = (desired_size - *sprite_size) / 2.; + viewport.set( + &update_state.graphics.render_state, + glam::vec2(desired_size.x, desired_size.y), + glam::vec2(translation.x, translation.y), + glam::Vec2::ONE, + ); + let callback = luminol_egui_wgpu::Callback::new_paint_callback( + response.rect, + Painter::new(sprite.prepare(&update_state.graphics)), + ); + ui.painter().add(callback); + } + + if response.clicked() && !is_open { + let selected = match data.clone() { + Some(path) => { + // FIXME error handling + let sprite = + Self::load_preview_sprite(update_state, &self.directory, &path) + .unwrap(); + Selected::Entry { path, sprite } + } + None => Selected::None, + }; + + // FIXME error handling + let mut entries: Vec<_> = update_state + .filesystem + .read_dir(&self.directory) + .unwrap() + .into_iter() + .map(|m| { + let path = m + .path + .strip_prefix(&self.directory) + .unwrap_or(&m.path) + .with_extension(""); + Entry { + path, + invalid: false, + } + }) + .collect(); + entries.sort_unstable(); + + self.state = State::Open { + filtered_entries: entries.clone(), + entries, + search_text: String::new(), + selected, + }; + } + if self.show_window(update_state, ui.ctx(), data) { + response.mark_changed(); + } + + response + } + } + + fn reset(&mut self, update_state: &mut luminol_core::UpdateState<'_>, data: Self::Data<'_>) { + self.update_graphic(update_state, data); // we need to update the button sprite to prevent desyncs + self.state = State::Closed; + } +} + +impl Modal { + fn load_preview_sprite( + update_state: &luminol_core::UpdateState<'_>, + directory: &camino::Utf8Path, + path: &camino::Utf8Path, + ) -> color_eyre::Result { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, directory, path) + .wrap_err("While loading a preview sprite")?; + + Ok(Self::create_preview_sprite_from_texture( + update_state, + &texture, + )) + } + + fn create_preview_sprite_from_texture( + update_state: &luminol_core::UpdateState<'_>, + texture: &Texture, + ) -> PreviewSprite { + let viewport = Viewport::new( + &update_state.graphics, + glam::vec2(texture.width() as f32, texture.height() as f32), + ); + + let sprite = Sprite::basic(&update_state.graphics, texture, &viewport); + PreviewSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport, + } + } + + fn update_graphic( + &mut self, + update_state: &UpdateState<'_>, + data: &Option, + ) { + self.button_sprite = data.as_ref().map(|path| { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, &self.directory, path) + .unwrap(); // FIXME + + let button_viewport = Viewport::new(&update_state.graphics, Default::default()); + let sprite = Sprite::basic(&update_state.graphics, &texture, &button_viewport); + ButtonSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport: button_viewport, + } + }); + } + + fn show_window( + &mut self, + update_state: &mut luminol_core::UpdateState<'_>, + ctx: &egui::Context, + data: &mut Option, + ) -> bool { + let mut win_open = true; + let mut keep_open = true; + let mut needs_save = false; + + let State::Open { + entries, + filtered_entries, + search_text, + selected, + } = &mut self.state + else { + return false; + }; + + egui::Window::new("Graphic Picker") + .resizable(true) + .open(&mut win_open) + .id(self.id_source.with("window")) + .show(ctx, |ui| { + egui::SidePanel::left(self.id_source.with("sidebar")).show_inside(ui, |ui| { + let out = egui::TextEdit::singleline(search_text) + .hint_text("Search 🔎") + .show(ui); + if out.response.changed() { + let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); + *filtered_entries = entries + .iter() + .filter(|entry| { + matcher + .fuzzy(entry.path.as_str(), search_text, false) + .is_some() + }) + .cloned() + .collect(); + } + + ui.separator(); + + // Get row height. + let row_height = ui.text_style_height(&egui::TextStyle::Body); // i do not trust this + // FIXME scroll to selected on first open + ui.with_cross_justify(|ui| { + egui::ScrollArea::vertical() + .auto_shrink([false, true]) + .show_rows( + ui, + row_height, + filtered_entries.len() + 1, + |ui, mut rows| { + if rows.contains(&0) { + let res = ui.selectable_label( + matches!(selected, Selected::None), + "(None)", + ); + if res.clicked() && !matches!(selected, Selected::None) { + *selected = Selected::None; + } + } + + // subtract 2 to account for (None) + rows.start = rows.start.saturating_sub(1); + rows.end = rows.end.saturating_sub(1); + + for i in filtered_entries[rows.clone()].iter_mut().enumerate() { + let (i, Entry { path, invalid }) = i; + let checked = matches!(selected, Selected::Entry { path: p, .. } if p == path); + let mut text = egui::RichText::new(path.as_str()); + if *invalid { + text = text.color(egui::Color32::LIGHT_RED); + } + let faint = (i + rows.start) % 2 == 1; + ui.with_stripe(faint, |ui| { + let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); + + if res.clicked() { + let sprite = Self::load_preview_sprite(update_state, &self.directory, path).unwrap(); + *selected = Selected::Entry { path:path.clone(), sprite }; + } + }); + } + }, + ); + }); + }); + + egui::TopBottomPanel::bottom(self.id_source.with("bottom")).show_inside(ui, |ui| { + ui.add_space(ui.style().spacing.item_spacing.y); + luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + }); + + egui::CentralPanel::default().show_inside(ui, |ui| { + egui::ScrollArea::both().auto_shrink([false,false]).show_viewport(ui, |ui, viewport| { + match selected { + Selected::None => {} + Selected::Entry { sprite,.. } => { + let (canvas_rect, _) = ui.allocate_exact_size( + sprite.sprite_size, + egui::Sense::focusable_noninteractive(), // FIXME screen reader hints + ); + + let absolute_scroll_rect = ui + .ctx() + .screen_rect() + .intersect(viewport.translate(canvas_rect.min.to_vec2())); + let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); + sprite.sprite.transform.set_position( + &update_state.graphics.render_state, + glam::vec2(-scroll_rect.left(), -scroll_rect.top()), + ); + + sprite.viewport.set( + &update_state.graphics.render_state, + glam::vec2(absolute_scroll_rect.width(), absolute_scroll_rect.height()), + glam::Vec2::ZERO, + glam::Vec2::ONE, + ); + + let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); + ui.painter() + .add(luminol_egui_wgpu::Callback::new_paint_callback( + absolute_scroll_rect, + painter, + )); + } + } + }); + }); + }); + + if needs_save { + match selected { + Selected::None => *data = None, + Selected::Entry { path, .. } => *data = Some(path.clone()), + } + + self.update_graphic(update_state, data); + } + + if !(win_open && keep_open) { + self.state = State::Closed; + } + + needs_save + } +} diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index c9019813..4b21bccc 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Lily Lyons +// Copyright (C) 2024 Lily Lyons // // This file is part of Luminol. // @@ -22,389 +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 color_eyre::eyre::Context; -use luminol_components::UiExt; -use luminol_core::prelude::*; - -pub struct Modal { - state: State, - id_source: egui::Id, - - button_size: egui::Vec2, - directory: camino::Utf8PathBuf, // do we make this &'static Utf8Path? - - button_sprite: Option, -} - -enum State { - Closed, - Open { - entries: Vec, - filtered_entries: Vec, - search_text: String, - - selected: Selected, - }, -} - -#[derive(Default)] -enum Selected { - #[default] - None, - Entry { - path: camino::Utf8PathBuf, - sprite: PreviewSprite, - }, -} - -struct ButtonSprite { - sprite: Sprite, - sprite_size: egui::Vec2, - viewport: Viewport, -} - -struct PreviewSprite { - sprite: Sprite, - sprite_size: egui::Vec2, - viewport: Viewport, -} - -#[derive(PartialEq, PartialOrd, Eq, Ord, Clone)] -struct Entry { - path: camino::Utf8PathBuf, - invalid: bool, -} - -impl Modal { - pub fn new( - update_state: &UpdateState<'_>, - directory: camino::Utf8PathBuf, - path: Option<&camino::Utf8Path>, - button_size: egui::Vec2, - id_source: impl Into, - ) -> Self { - let button_sprite = path.map(|path| { - let texture = update_state - .graphics - .texture_loader - .load_now_dir(update_state.filesystem, &directory, path) - .unwrap(); // FIXME - - let button_viewport = Viewport::new(&update_state.graphics, Default::default()); - let sprite = Sprite::basic(&update_state.graphics, &texture, &button_viewport); - ButtonSprite { - sprite, - sprite_size: texture.size_vec2(), - viewport: button_viewport, - } - }); - - Self { - state: State::Closed, - id_source: id_source.into(), - button_size, - directory, - button_sprite, - } - } -} - -impl luminol_core::Modal for Modal { - type Data<'m> = &'m mut Option; - - fn button<'m>( - &'m mut self, - data: Self::Data<'m>, - update_state: &'m mut luminol_core::UpdateState<'_>, - ) -> impl egui::Widget + 'm { - |ui: &mut egui::Ui| { - let desired_size = self.button_size + ui.spacing().button_padding * 2.0; - let (rect, mut response) = ui.allocate_at_least(desired_size, egui::Sense::click()); - - let is_open = matches!(self.state, State::Open { .. }); - let visuals = ui.style().interact_selectable(&response, is_open); - let rect = rect.expand(visuals.expansion); - ui.painter() - .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); - - if let Some(ButtonSprite { - sprite, - sprite_size, - viewport, - }) = &mut self.button_sprite - { - let translation = (desired_size - *sprite_size) / 2.; - viewport.set( - &update_state.graphics.render_state, - glam::vec2(desired_size.x, desired_size.y), - glam::vec2(translation.x, translation.y), - glam::Vec2::ONE, - ); - let callback = luminol_egui_wgpu::Callback::new_paint_callback( - response.rect, - Painter::new(sprite.prepare(&update_state.graphics)), - ); - ui.painter().add(callback); - } - - if response.clicked() && !is_open { - let selected = match data.clone() { - Some(path) => { - // FIXME error handling - let sprite = - Self::load_preview_sprite(update_state, &self.directory, &path) - .unwrap(); - Selected::Entry { path, sprite } - } - None => Selected::None, - }; - - // FIXME error handling - let mut entries: Vec<_> = update_state - .filesystem - .read_dir(&self.directory) - .unwrap() - .into_iter() - .map(|m| { - let path = m - .path - .strip_prefix(&self.directory) - .unwrap_or(&m.path) - .with_extension(""); - Entry { - path, - invalid: false, - } - }) - .collect(); - entries.sort_unstable(); - - self.state = State::Open { - filtered_entries: entries.clone(), - entries, - search_text: String::new(), - selected, - }; - } - if self.show_window(update_state, ui.ctx(), data) { - response.mark_changed(); - } - - response - } - } - - fn reset(&mut self, update_state: &mut luminol_core::UpdateState<'_>, data: Self::Data<'_>) { - self.update_graphic(update_state, data); // we need to update the button sprite to prevent desyncs - self.state = State::Closed; - } -} - -impl Modal { - fn load_preview_sprite( - update_state: &luminol_core::UpdateState<'_>, - directory: &camino::Utf8Path, - path: &camino::Utf8Path, - ) -> color_eyre::Result { - let texture = update_state - .graphics - .texture_loader - .load_now_dir(update_state.filesystem, directory, path) - .wrap_err("While loading a preview sprite")?; - - Ok(Self::create_preview_sprite_from_texture( - update_state, - &texture, - )) - } - - fn create_preview_sprite_from_texture( - update_state: &luminol_core::UpdateState<'_>, - texture: &Texture, - ) -> PreviewSprite { - let viewport = Viewport::new( - &update_state.graphics, - glam::vec2(texture.width() as f32, texture.height() as f32), - ); - - let sprite = Sprite::basic(&update_state.graphics, texture, &viewport); - PreviewSprite { - sprite, - sprite_size: texture.size_vec2(), - viewport, - } - } - - fn update_graphic( - &mut self, - update_state: &UpdateState<'_>, - data: &Option, - ) { - self.button_sprite = data.as_ref().map(|path| { - let texture = update_state - .graphics - .texture_loader - .load_now_dir(update_state.filesystem, &self.directory, path) - .unwrap(); // FIXME - - let button_viewport = Viewport::new(&update_state.graphics, Default::default()); - let sprite = Sprite::basic(&update_state.graphics, &texture, &button_viewport); - ButtonSprite { - sprite, - sprite_size: texture.size_vec2(), - viewport: button_viewport, - } - }); - } - - fn show_window( - &mut self, - update_state: &mut luminol_core::UpdateState<'_>, - ctx: &egui::Context, - data: &mut Option, - ) -> bool { - let mut win_open = true; - let mut keep_open = true; - let mut needs_save = false; - - let State::Open { - entries, - filtered_entries, - search_text, - selected, - } = &mut self.state - else { - return false; - }; - - egui::Window::new("Graphic Picker") - .resizable(true) - .open(&mut win_open) - .id(self.id_source.with("window")) - .show(ctx, |ui| { - egui::SidePanel::left(self.id_source.with("sidebar")).show_inside(ui, |ui| { - let out = egui::TextEdit::singleline(search_text) - .hint_text("Search 🔎") - .show(ui); - if out.response.changed() { - let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); - *filtered_entries = entries - .iter() - .filter(|entry| { - matcher - .fuzzy(entry.path.as_str(), search_text, false) - .is_some() - }) - .cloned() - .collect(); - } - - ui.separator(); - - // Get row height. - let row_height = ui.text_style_height(&egui::TextStyle::Body); // i do not trust this - // FIXME scroll to selected on first open - ui.with_cross_justify(|ui| { - egui::ScrollArea::vertical() - .auto_shrink([false, true]) - .show_rows( - ui, - row_height, - filtered_entries.len() + 1, - |ui, mut rows| { - if rows.contains(&0) { - let res = ui.selectable_label( - matches!(selected, Selected::None), - "(None)", - ); - if res.clicked() && !matches!(selected, Selected::None) { - *selected = Selected::None; - } - } - - // subtract 2 to account for (None) - rows.start = rows.start.saturating_sub(1); - rows.end = rows.end.saturating_sub(1); - - for i in filtered_entries[rows.clone()].iter_mut().enumerate() { - let (i, Entry { path, invalid }) = i; - let checked = matches!(selected, Selected::Entry { path: p, .. } if p == path); - let mut text = egui::RichText::new(path.as_str()); - if *invalid { - text = text.color(egui::Color32::LIGHT_RED); - } - let faint = (i + rows.start) % 2 == 1; - ui.with_stripe(faint, |ui| { - let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); - - if res.clicked() { - let sprite = Self::load_preview_sprite(update_state, &self.directory, path).unwrap(); - *selected = Selected::Entry { path:path.clone(), sprite }; - } - }); - } - }, - ); - }); - }); - - egui::TopBottomPanel::bottom(self.id_source.with("bottom")).show_inside(ui, |ui| { - ui.add_space(ui.style().spacing.item_spacing.y); - luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); - }); - - egui::CentralPanel::default().show_inside(ui, |ui| { - egui::ScrollArea::both().auto_shrink([false,false]).show_viewport(ui, |ui, viewport| { - match selected { - Selected::None => {} - Selected::Entry { sprite,.. } => { - let (canvas_rect, _) = ui.allocate_exact_size( - sprite.sprite_size, - egui::Sense::focusable_noninteractive(), // FIXME screen reader hints - ); - - let absolute_scroll_rect = ui - .ctx() - .screen_rect() - .intersect(viewport.translate(canvas_rect.min.to_vec2())); - let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); - sprite.sprite.transform.set_position( - &update_state.graphics.render_state, - glam::vec2(-scroll_rect.left(), -scroll_rect.top()), - ); - - sprite.viewport.set( - &update_state.graphics.render_state, - glam::vec2(absolute_scroll_rect.width(), absolute_scroll_rect.height()), - glam::Vec2::ZERO, - glam::Vec2::ONE, - ); - - let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); - ui.painter() - .add(luminol_egui_wgpu::Callback::new_paint_callback( - absolute_scroll_rect, - painter, - )); - } - } - }); - }); - }); - - if needs_save { - match selected { - Selected::None => *data = None, - Selected::Entry { path, .. } => *data = Some(path.clone()), - } - - self.update_graphic(update_state, data); - } - - if !(win_open && keep_open) { - self.state = State::Closed; - } - - needs_save - } -} +pub mod basic; diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index f2f7ef47..a8364d2a 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -25,12 +25,15 @@ use luminol_components::UiExt; use luminol_core::Modal; +use luminol_modals::graphic_picker::basic::Modal as GraphicPicker; +use luminol_modals::sound_picker::Modal as SoundPicker; + /// Database - Items management window. pub struct Window { selected_item_name: Option, - menu_se_picker: luminol_modals::sound_picker::Modal, - graphic_picker: luminol_modals::graphic_picker::Modal, + menu_se_picker: SoundPicker, + graphic_picker: GraphicPicker, previous_item: Option, @@ -43,11 +46,8 @@ impl Window { let item = &items.data[0]; Self { selected_item_name: None, - menu_se_picker: luminol_modals::sound_picker::Modal::new( - luminol_audio::Source::SE, - "item_menu_se_picker", - ), - graphic_picker: luminol_modals::graphic_picker::Modal::new( + menu_se_picker: SoundPicker::new(luminol_audio::Source::SE, "item_menu_se_picker"), + graphic_picker: GraphicPicker::new( update_state, camino::Utf8PathBuf::from("Graphics/Icons"), item.icon_name.as_deref(), From dba0f48c29f5c3c1188a094675cd1bab4605c982 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 06:02:44 -0700 Subject: [PATCH 57/67] Add hue picker --- crates/modals/src/graphic_picker/hue.rs | 416 ++++++++++++++++++++++++ crates/modals/src/graphic_picker/mod.rs | 31 ++ 2 files changed, 447 insertions(+) create mode 100644 crates/modals/src/graphic_picker/hue.rs diff --git a/crates/modals/src/graphic_picker/hue.rs b/crates/modals/src/graphic_picker/hue.rs new file mode 100644 index 00000000..cbff9b8f --- /dev/null +++ b/crates/modals/src/graphic_picker/hue.rs @@ -0,0 +1,416 @@ +// Copyright (C) 2023 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 color_eyre::eyre::Context; +use luminol_components::UiExt; +use luminol_core::prelude::*; + +pub struct Modal { + state: State, + id_source: egui::Id, + + button_size: egui::Vec2, + directory: camino::Utf8PathBuf, // do we make this &'static Utf8Path? + + button_sprite: Option, +} + +enum State { + Closed, + Open { + entries: Vec, + filtered_entries: Vec, + search_text: String, + + hue: i32, + + selected: Selected, + }, +} + +#[derive(Default)] +enum Selected { + #[default] + None, + Entry { + path: camino::Utf8PathBuf, + sprite: PreviewSprite, + }, +} + +struct ButtonSprite { + sprite: Sprite, + sprite_size: egui::Vec2, + viewport: Viewport, +} + +struct PreviewSprite { + sprite: Sprite, + sprite_size: egui::Vec2, + viewport: Viewport, +} + +#[derive(PartialEq, PartialOrd, Eq, Ord, Clone)] +struct Entry { + path: camino::Utf8PathBuf, + invalid: bool, +} + +impl Modal { + pub fn new( + update_state: &UpdateState<'_>, + directory: camino::Utf8PathBuf, + path: Option<&camino::Utf8Path>, + button_size: egui::Vec2, + id_source: impl Into, + ) -> Self { + let button_sprite = path.map(|path| { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, &directory, path) + .unwrap(); // FIXME + + let button_viewport = Viewport::new(&update_state.graphics, Default::default()); + let sprite = Sprite::basic(&update_state.graphics, &texture, &button_viewport); + ButtonSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport: button_viewport, + } + }); + + Self { + state: State::Closed, + id_source: id_source.into(), + button_size, + directory, + button_sprite, + } + } +} + +impl luminol_core::Modal for Modal { + type Data<'m> = (&'m mut Option, &'m mut i32); + + fn button<'m>( + &'m mut self, + data: Self::Data<'m>, + update_state: &'m mut luminol_core::UpdateState<'_>, + ) -> impl egui::Widget + 'm { + |ui: &mut egui::Ui| { + let desired_size = self.button_size + ui.spacing().button_padding * 2.0; + let (rect, mut response) = ui.allocate_at_least(desired_size, egui::Sense::click()); + + let is_open = matches!(self.state, State::Open { .. }); + let visuals = ui.style().interact_selectable(&response, is_open); + let rect = rect.expand(visuals.expansion); + ui.painter() + .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); + + if let Some(ButtonSprite { + sprite, + sprite_size, + viewport, + }) = &mut self.button_sprite + { + let translation = (desired_size - *sprite_size) / 2.; + viewport.set( + &update_state.graphics.render_state, + glam::vec2(desired_size.x, desired_size.y), + glam::vec2(translation.x, translation.y), + glam::Vec2::ONE, + ); + let callback = luminol_egui_wgpu::Callback::new_paint_callback( + response.rect, + Painter::new(sprite.prepare(&update_state.graphics)), + ); + ui.painter().add(callback); + } + + if response.clicked() && !is_open { + let selected = match data.0.clone() { + Some(path) => { + // FIXME error handling + let sprite = + Self::load_preview_sprite(update_state, &self.directory, &path) + .unwrap(); + Selected::Entry { path, sprite } + } + None => Selected::None, + }; + + // FIXME error handling + let mut entries: Vec<_> = update_state + .filesystem + .read_dir(&self.directory) + .unwrap() + .into_iter() + .map(|m| { + let path = m + .path + .strip_prefix(&self.directory) + .unwrap_or(&m.path) + .with_extension(""); + Entry { + path, + invalid: false, + } + }) + .collect(); + entries.sort_unstable(); + + self.state = State::Open { + filtered_entries: entries.clone(), + entries, + hue: *data.1, + search_text: String::new(), + selected, + }; + } + if self.show_window(update_state, ui.ctx(), data) { + response.mark_changed(); + } + + response + } + } + + fn reset(&mut self, update_state: &mut luminol_core::UpdateState<'_>, data: Self::Data<'_>) { + self.update_graphic(update_state, data); // we need to update the button sprite to prevent desyncs + self.state = State::Closed; + } +} + +impl Modal { + fn load_preview_sprite( + update_state: &luminol_core::UpdateState<'_>, + directory: &camino::Utf8Path, + path: &camino::Utf8Path, + ) -> color_eyre::Result { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, directory, path) + .wrap_err("While loading a preview sprite")?; + + Ok(Self::create_preview_sprite_from_texture( + update_state, + &texture, + )) + } + + fn create_preview_sprite_from_texture( + update_state: &luminol_core::UpdateState<'_>, + texture: &Texture, + ) -> PreviewSprite { + let viewport = Viewport::new( + &update_state.graphics, + glam::vec2(texture.width() as f32, texture.height() as f32), + ); + + let sprite = Sprite::basic(&update_state.graphics, texture, &viewport); + PreviewSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport, + } + } + + fn update_graphic( + &mut self, + update_state: &UpdateState<'_>, + data: (&mut Option, &mut i32), + ) { + self.button_sprite = data.0.as_ref().map(|path| { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, &self.directory, path) + .unwrap(); // FIXME + + let button_viewport = Viewport::new(&update_state.graphics, Default::default()); + let sprite = + Sprite::basic_hue(&update_state.graphics, *data.1, &texture, &button_viewport); + ButtonSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport: button_viewport, + } + }); + } + + fn show_window( + &mut self, + update_state: &mut luminol_core::UpdateState<'_>, + ctx: &egui::Context, + data: (&mut Option, &mut i32), + ) -> bool { + let mut win_open = true; + let mut keep_open = true; + let mut needs_save = false; + + let State::Open { + entries, + filtered_entries, + hue, + search_text, + selected, + } = &mut self.state + else { + return false; + }; + + egui::Window::new("Graphic Picker") + .resizable(true) + .open(&mut win_open) + .id(self.id_source.with("window")) + .show(ctx, |ui| { + egui::SidePanel::left(self.id_source.with("sidebar")).show_inside(ui, |ui| { + let out = egui::TextEdit::singleline(search_text) + .hint_text("Search 🔎") + .show(ui); + if out.response.changed() { + let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); + *filtered_entries = entries + .iter() + .filter(|entry| { + matcher + .fuzzy(entry.path.as_str(), search_text, false) + .is_some() + }) + .cloned() + .collect(); + } + + ui.separator(); + + // Get row height. + let row_height = ui.text_style_height(&egui::TextStyle::Body); // i do not trust this + // FIXME scroll to selected on first open + ui.with_cross_justify(|ui| { + egui::ScrollArea::vertical() + .auto_shrink([false, true]) + .show_rows( + ui, + row_height, + filtered_entries.len() + 1, + |ui, mut rows| { + if rows.contains(&0) { + let res = ui.selectable_label( + matches!(selected, Selected::None), + "(None)", + ); + if res.clicked() && !matches!(selected, Selected::None) { + *selected = Selected::None; + } + } + + // subtract 2 to account for (None) + rows.start = rows.start.saturating_sub(1); + rows.end = rows.end.saturating_sub(1); + + for i in filtered_entries[rows.clone()].iter_mut().enumerate() { + let (i, Entry { path, invalid }) = i; + let checked = matches!(selected, Selected::Entry { path: p, .. } if p == path); + let mut text = egui::RichText::new(path.as_str()); + if *invalid { + text = text.color(egui::Color32::LIGHT_RED); + } + let faint = (i + rows.start) % 2 == 1; + ui.with_stripe(faint, |ui| { + let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); + + if res.clicked() { + let sprite = Self::load_preview_sprite(update_state, &self.directory, path).unwrap(); + *selected = Selected::Entry { path:path.clone(), sprite }; + } + }); + } + }, + ); + }); + }); + + egui::TopBottomPanel::bottom(self.id_source.with("bottom")).show_inside(ui, |ui| { + ui.add_space(ui.style().spacing.item_spacing.y); + luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + }); + + egui::CentralPanel::default().show_inside(ui, |ui| { + egui::ScrollArea::both().auto_shrink([false,false]).show_viewport(ui, |ui, viewport| { + match selected { + Selected::None => {} + Selected::Entry { sprite,.. } => { + let (canvas_rect, _) = ui.allocate_exact_size( + sprite.sprite_size, + egui::Sense::focusable_noninteractive(), // FIXME screen reader hints + ); + + let absolute_scroll_rect = ui + .ctx() + .screen_rect() + .intersect(viewport.translate(canvas_rect.min.to_vec2())); + let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); + sprite.sprite.transform.set_position( + &update_state.graphics.render_state, + glam::vec2(-scroll_rect.left(), -scroll_rect.top()), + ); + + sprite.viewport.set( + &update_state.graphics.render_state, + glam::vec2(absolute_scroll_rect.width(), absolute_scroll_rect.height()), + glam::Vec2::ZERO, + glam::Vec2::ONE, + ); + + let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); + ui.painter() + .add(luminol_egui_wgpu::Callback::new_paint_callback( + absolute_scroll_rect, + painter, + )); + } + } + }); + }); + }); + + if needs_save { + match selected { + Selected::None => *data.0 = None, + Selected::Entry { path, .. } => *data.0 = Some(path.clone()), + } + *data.1 = *hue; + + self.update_graphic(update_state, data); + } + + if !(win_open && keep_open) { + self.state = State::Closed; + } + + needs_save + } +} diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index 4b21bccc..52b761f1 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -22,4 +22,35 @@ // terms of the Steamworks API by Valve Corporation, the licensors of this // Program grant you additional permission to convey the resulting work. +use luminol_core::prelude::*; + pub mod basic; +pub mod hue; + +#[derive(Default)] +enum Selected { + #[default] + None, + Entry { + path: camino::Utf8PathBuf, + sprite: PreviewSprite, + }, +} + +struct ButtonSprite { + sprite: Sprite, + sprite_size: egui::Vec2, + viewport: Viewport, +} + +struct PreviewSprite { + sprite: Sprite, + sprite_size: egui::Vec2, + viewport: Viewport, +} + +#[derive(PartialEq, PartialOrd, Eq, Ord, Clone)] +struct Entry { + path: camino::Utf8PathBuf, + invalid: bool, +} From fa1805f3b76a26cd243732e55e57d5d141a51e8d Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 06:29:09 -0700 Subject: [PATCH 58/67] Deduplicate graphic picker behaviour --- crates/modals/src/graphic_picker/basic.rs | 158 ++++----------------- crates/modals/src/graphic_picker/hue.rs | 161 ++++------------------ crates/modals/src/graphic_picker/mod.rs | 141 +++++++++++++++++++ 3 files changed, 193 insertions(+), 267 deletions(-) diff --git a/crates/modals/src/graphic_picker/basic.rs b/crates/modals/src/graphic_picker/basic.rs index c9019813..7da0c842 100644 --- a/crates/modals/src/graphic_picker/basic.rs +++ b/crates/modals/src/graphic_picker/basic.rs @@ -26,6 +26,8 @@ use color_eyre::eyre::Context; use luminol_components::UiExt; use luminol_core::prelude::*; +use super::{ButtonSprite, Entry, PreviewSprite, Selected}; + pub struct Modal { state: State, id_source: egui::Id, @@ -47,34 +49,6 @@ enum State { }, } -#[derive(Default)] -enum Selected { - #[default] - None, - Entry { - path: camino::Utf8PathBuf, - sprite: PreviewSprite, - }, -} - -struct ButtonSprite { - sprite: Sprite, - sprite_size: egui::Vec2, - viewport: Viewport, -} - -struct PreviewSprite { - sprite: Sprite, - sprite_size: egui::Vec2, - viewport: Viewport, -} - -#[derive(PartialEq, PartialOrd, Eq, Ord, Clone)] -struct Entry { - path: camino::Utf8PathBuf, - invalid: bool, -} - impl Modal { pub fn new( update_state: &UpdateState<'_>, @@ -119,33 +93,14 @@ impl luminol_core::Modal for Modal { ) -> impl egui::Widget + 'm { |ui: &mut egui::Ui| { let desired_size = self.button_size + ui.spacing().button_padding * 2.0; - let (rect, mut response) = ui.allocate_at_least(desired_size, egui::Sense::click()); - let is_open = matches!(self.state, State::Open { .. }); - let visuals = ui.style().interact_selectable(&response, is_open); - let rect = rect.expand(visuals.expansion); - ui.painter() - .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); - - if let Some(ButtonSprite { - sprite, - sprite_size, - viewport, - }) = &mut self.button_sprite - { - let translation = (desired_size - *sprite_size) / 2.; - viewport.set( - &update_state.graphics.render_state, - glam::vec2(desired_size.x, desired_size.y), - glam::vec2(translation.x, translation.y), - glam::Vec2::ONE, - ); - let callback = luminol_egui_wgpu::Callback::new_paint_callback( - response.rect, - Painter::new(sprite.prepare(&update_state.graphics)), - ); - ui.painter().add(callback); - } + let mut response = ButtonSprite::ui( + self.button_sprite.as_mut(), + ui, + update_state, + is_open, + desired_size, + ); if response.clicked() && !is_open { let selected = match data.clone() { @@ -159,25 +114,7 @@ impl luminol_core::Modal for Modal { None => Selected::None, }; - // FIXME error handling - let mut entries: Vec<_> = update_state - .filesystem - .read_dir(&self.directory) - .unwrap() - .into_iter() - .map(|m| { - let path = m - .path - .strip_prefix(&self.directory) - .unwrap_or(&m.path) - .with_extension(""); - Entry { - path, - invalid: false, - } - }) - .collect(); - entries.sort_unstable(); + let entries = Entry::load(update_state, &self.directory); self.state = State::Open { filtered_entries: entries.clone(), @@ -287,16 +224,7 @@ impl Modal { .hint_text("Search 🔎") .show(ui); if out.response.changed() { - let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); - *filtered_entries = entries - .iter() - .filter(|entry| { - matcher - .fuzzy(entry.path.as_str(), search_text, false) - .is_some() - }) - .cloned() - .collect(); + *filtered_entries = Entry::filter(entries, search_text); } ui.separator(); @@ -326,23 +254,14 @@ impl Modal { rows.start = rows.start.saturating_sub(1); rows.end = rows.end.saturating_sub(1); - for i in filtered_entries[rows.clone()].iter_mut().enumerate() { - let (i, Entry { path, invalid }) = i; - let checked = matches!(selected, Selected::Entry { path: p, .. } if p == path); - let mut text = egui::RichText::new(path.as_str()); - if *invalid { - text = text.color(egui::Color32::LIGHT_RED); - } - let faint = (i + rows.start) % 2 == 1; - ui.with_stripe(faint, |ui| { - let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); - - if res.clicked() { - let sprite = Self::load_preview_sprite(update_state, &self.directory, path).unwrap(); - *selected = Selected::Entry { path:path.clone(), sprite }; - } - }); - } + Entry::ui(filtered_entries, ui, rows, selected, |path| { + Self::load_preview_sprite( + update_state, + &self.directory, + path, + ) + .unwrap() + }) }, ); }); @@ -354,41 +273,14 @@ impl Modal { }); egui::CentralPanel::default().show_inside(ui, |ui| { - egui::ScrollArea::both().auto_shrink([false,false]).show_viewport(ui, |ui, viewport| { - match selected { + egui::ScrollArea::both() + .auto_shrink([false, false]) + .show_viewport(ui, |ui, viewport| match selected { Selected::None => {} - Selected::Entry { sprite,.. } => { - let (canvas_rect, _) = ui.allocate_exact_size( - sprite.sprite_size, - egui::Sense::focusable_noninteractive(), // FIXME screen reader hints - ); - - let absolute_scroll_rect = ui - .ctx() - .screen_rect() - .intersect(viewport.translate(canvas_rect.min.to_vec2())); - let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); - sprite.sprite.transform.set_position( - &update_state.graphics.render_state, - glam::vec2(-scroll_rect.left(), -scroll_rect.top()), - ); - - sprite.viewport.set( - &update_state.graphics.render_state, - glam::vec2(absolute_scroll_rect.width(), absolute_scroll_rect.height()), - glam::Vec2::ZERO, - glam::Vec2::ONE, - ); - - let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); - ui.painter() - .add(luminol_egui_wgpu::Callback::new_paint_callback( - absolute_scroll_rect, - painter, - )); + Selected::Entry { sprite, .. } => { + sprite.ui(ui, viewport, update_state); } - } - }); + }); }); }); diff --git a/crates/modals/src/graphic_picker/hue.rs b/crates/modals/src/graphic_picker/hue.rs index cbff9b8f..8ee4e68d 100644 --- a/crates/modals/src/graphic_picker/hue.rs +++ b/crates/modals/src/graphic_picker/hue.rs @@ -26,6 +26,8 @@ use color_eyre::eyre::Context; use luminol_components::UiExt; use luminol_core::prelude::*; +use super::{ButtonSprite, Entry, PreviewSprite, Selected}; + pub struct Modal { state: State, id_source: egui::Id, @@ -49,39 +51,12 @@ enum State { }, } -#[derive(Default)] -enum Selected { - #[default] - None, - Entry { - path: camino::Utf8PathBuf, - sprite: PreviewSprite, - }, -} - -struct ButtonSprite { - sprite: Sprite, - sprite_size: egui::Vec2, - viewport: Viewport, -} - -struct PreviewSprite { - sprite: Sprite, - sprite_size: egui::Vec2, - viewport: Viewport, -} - -#[derive(PartialEq, PartialOrd, Eq, Ord, Clone)] -struct Entry { - path: camino::Utf8PathBuf, - invalid: bool, -} - impl Modal { pub fn new( update_state: &UpdateState<'_>, directory: camino::Utf8PathBuf, path: Option<&camino::Utf8Path>, + hue: i32, button_size: egui::Vec2, id_source: impl Into, ) -> Self { @@ -93,7 +68,7 @@ impl Modal { .unwrap(); // FIXME let button_viewport = Viewport::new(&update_state.graphics, Default::default()); - let sprite = Sprite::basic(&update_state.graphics, &texture, &button_viewport); + let sprite = Sprite::basic_hue(&update_state.graphics, hue, &texture, &button_viewport); ButtonSprite { sprite, sprite_size: texture.size_vec2(), @@ -121,33 +96,14 @@ impl luminol_core::Modal for Modal { ) -> impl egui::Widget + 'm { |ui: &mut egui::Ui| { let desired_size = self.button_size + ui.spacing().button_padding * 2.0; - let (rect, mut response) = ui.allocate_at_least(desired_size, egui::Sense::click()); - let is_open = matches!(self.state, State::Open { .. }); - let visuals = ui.style().interact_selectable(&response, is_open); - let rect = rect.expand(visuals.expansion); - ui.painter() - .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); - - if let Some(ButtonSprite { - sprite, - sprite_size, - viewport, - }) = &mut self.button_sprite - { - let translation = (desired_size - *sprite_size) / 2.; - viewport.set( - &update_state.graphics.render_state, - glam::vec2(desired_size.x, desired_size.y), - glam::vec2(translation.x, translation.y), - glam::Vec2::ONE, - ); - let callback = luminol_egui_wgpu::Callback::new_paint_callback( - response.rect, - Painter::new(sprite.prepare(&update_state.graphics)), - ); - ui.painter().add(callback); - } + let mut response = ButtonSprite::ui( + self.button_sprite.as_mut(), + ui, + update_state, + is_open, + desired_size, + ); if response.clicked() && !is_open { let selected = match data.0.clone() { @@ -161,25 +117,7 @@ impl luminol_core::Modal for Modal { None => Selected::None, }; - // FIXME error handling - let mut entries: Vec<_> = update_state - .filesystem - .read_dir(&self.directory) - .unwrap() - .into_iter() - .map(|m| { - let path = m - .path - .strip_prefix(&self.directory) - .unwrap_or(&m.path) - .with_extension(""); - Entry { - path, - invalid: false, - } - }) - .collect(); - entries.sort_unstable(); + let entries = Entry::load(update_state, &self.directory); self.state = State::Open { filtered_entries: entries.clone(), @@ -292,16 +230,7 @@ impl Modal { .hint_text("Search 🔎") .show(ui); if out.response.changed() { - let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); - *filtered_entries = entries - .iter() - .filter(|entry| { - matcher - .fuzzy(entry.path.as_str(), search_text, false) - .is_some() - }) - .cloned() - .collect(); + *filtered_entries = Entry::filter(entries, search_text); } ui.separator(); @@ -331,23 +260,14 @@ impl Modal { rows.start = rows.start.saturating_sub(1); rows.end = rows.end.saturating_sub(1); - for i in filtered_entries[rows.clone()].iter_mut().enumerate() { - let (i, Entry { path, invalid }) = i; - let checked = matches!(selected, Selected::Entry { path: p, .. } if p == path); - let mut text = egui::RichText::new(path.as_str()); - if *invalid { - text = text.color(egui::Color32::LIGHT_RED); - } - let faint = (i + rows.start) % 2 == 1; - ui.with_stripe(faint, |ui| { - let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); - - if res.clicked() { - let sprite = Self::load_preview_sprite(update_state, &self.directory, path).unwrap(); - *selected = Selected::Entry { path:path.clone(), sprite }; - } - }); - } + Entry::ui(filtered_entries, ui, rows, selected, |path| { + Self::load_preview_sprite( + update_state, + &self.directory, + path, + ) + .unwrap() + }) }, ); }); @@ -359,41 +279,14 @@ impl Modal { }); egui::CentralPanel::default().show_inside(ui, |ui| { - egui::ScrollArea::both().auto_shrink([false,false]).show_viewport(ui, |ui, viewport| { - match selected { + egui::ScrollArea::both() + .auto_shrink([false, false]) + .show_viewport(ui, |ui, viewport| match selected { Selected::None => {} - Selected::Entry { sprite,.. } => { - let (canvas_rect, _) = ui.allocate_exact_size( - sprite.sprite_size, - egui::Sense::focusable_noninteractive(), // FIXME screen reader hints - ); - - let absolute_scroll_rect = ui - .ctx() - .screen_rect() - .intersect(viewport.translate(canvas_rect.min.to_vec2())); - let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); - sprite.sprite.transform.set_position( - &update_state.graphics.render_state, - glam::vec2(-scroll_rect.left(), -scroll_rect.top()), - ); - - sprite.viewport.set( - &update_state.graphics.render_state, - glam::vec2(absolute_scroll_rect.width(), absolute_scroll_rect.height()), - glam::Vec2::ZERO, - glam::Vec2::ONE, - ); - - let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); - ui.painter() - .add(luminol_egui_wgpu::Callback::new_paint_callback( - absolute_scroll_rect, - painter, - )); + Selected::Entry { sprite, .. } => { + sprite.ui(ui, viewport, update_state); } - } - }); + }); }); }); diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index 52b761f1..5031ea16 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -22,6 +22,7 @@ // 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; use luminol_core::prelude::*; pub mod basic; @@ -54,3 +55,143 @@ struct Entry { path: camino::Utf8PathBuf, invalid: bool, } + +impl ButtonSprite { + pub fn ui( + this: Option<&mut Self>, + ui: &mut egui::Ui, + update_state: &UpdateState<'_>, + is_open: bool, + desired_size: egui::Vec2, + ) -> egui::Response { + let (rect, response) = ui.allocate_at_least(desired_size, egui::Sense::click()); + + let visuals = ui.style().interact_selectable(&response, is_open); + let rect = rect.expand(visuals.expansion); + ui.painter() + .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); + + if let Some(this) = this { + let viewport_size = rect.size(); + let translation = (viewport_size - this.sprite_size) / 2.; + this.viewport.set( + &update_state.graphics.render_state, + glam::vec2(viewport_size.x, viewport_size.y), + glam::vec2(translation.x, translation.y), + glam::Vec2::ONE, + ); + let callback = luminol_egui_wgpu::Callback::new_paint_callback( + rect, + Painter::new(this.sprite.prepare(&update_state.graphics)), + ); + ui.painter().add(callback); + } + + response + } +} + +impl Entry { + fn load( + // FIXME error handling + update_state: &UpdateState<'_>, + directory: &camino::Utf8Path, + ) -> Vec { + let mut entries: Vec<_> = update_state + .filesystem + .read_dir(directory) + .unwrap() + .into_iter() + .map(|m| { + let path = m + .path + .strip_prefix(directory) + .unwrap_or(&m.path) + .with_extension(""); + Entry { + path, + invalid: false, + } + }) + .collect(); + entries.sort_unstable(); + entries + } + + fn filter(entries: &[Self], filter: &str) -> Vec { + let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); + entries + .iter() + .filter(|entry| matcher.fuzzy(entry.path.as_str(), filter, false).is_some()) + .cloned() + .collect() + } + + fn ui( + entries: &mut [Self], + ui: &mut egui::Ui, + rows: std::ops::Range, + selected: &mut Selected, + load_preview_sprite: impl Fn(&camino::Utf8Path) -> PreviewSprite, + ) { + for i in entries[rows.clone()].iter_mut().enumerate() { + let (i, Self { path, invalid }) = i; + let checked = matches!(selected, Selected::Entry { path: p, .. } if p == path); + let mut text = egui::RichText::new(path.as_str()); + if *invalid { + text = text.color(egui::Color32::LIGHT_RED); + } + let faint = (i + rows.start) % 2 == 1; + ui.with_stripe(faint, |ui| { + let res = ui.add_enabled(!*invalid, egui::SelectableLabel::new(checked, text)); + + if res.clicked() { + *selected = Selected::Entry { + path: path.clone(), + sprite: load_preview_sprite(path), + }; + } + }); + } + } +} + +impl PreviewSprite { + fn ui( + &mut self, + ui: &mut egui::Ui, + viewport: egui::Rect, + update_state: &UpdateState<'_>, + ) -> egui::Response { + let (canvas_rect, response) = ui.allocate_exact_size( + self.sprite_size, + egui::Sense::focusable_noninteractive(), // FIXME screen reader hints + ); + + let absolute_scroll_rect = ui + .ctx() + .screen_rect() + .intersect(viewport.translate(canvas_rect.min.to_vec2())); + let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); + self.sprite.transform.set_position( + &update_state.graphics.render_state, + glam::vec2(-scroll_rect.left(), -scroll_rect.top()), + ); + + self.viewport.set( + &update_state.graphics.render_state, + glam::vec2(absolute_scroll_rect.width(), absolute_scroll_rect.height()), + glam::Vec2::ZERO, + glam::Vec2::ONE, + ); + + let painter = Painter::new(self.sprite.prepare(&update_state.graphics)); + ui.painter() + .add(luminol_egui_wgpu::Callback::new_paint_callback( + absolute_scroll_rect, + painter, + )); + + response + } +} From f90c6e429a12ed6e4ab11a22e297847add75f96f Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 06:30:45 -0700 Subject: [PATCH 59/67] Move event graphic picker into graphic_picker/ --- .../event.rs} | 0 crates/modals/src/graphic_picker/mod.rs | 1 + crates/modals/src/lib.rs | 2 -- crates/ui/src/windows/event_edit.rs | 21 +++++++++++-------- 4 files changed, 13 insertions(+), 11 deletions(-) rename crates/modals/src/{event_graphic_picker.rs => graphic_picker/event.rs} (100%) diff --git a/crates/modals/src/event_graphic_picker.rs b/crates/modals/src/graphic_picker/event.rs similarity index 100% rename from crates/modals/src/event_graphic_picker.rs rename to crates/modals/src/graphic_picker/event.rs diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index 5031ea16..db85c3d5 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -26,6 +26,7 @@ use luminol_components::UiExt; use luminol_core::prelude::*; pub mod basic; +pub mod event; pub mod hue; #[derive(Default)] diff --git a/crates/modals/src/lib.rs b/crates/modals/src/lib.rs index 6b118cd1..f6a702a5 100644 --- a/crates/modals/src/lib.rs +++ b/crates/modals/src/lib.rs @@ -27,5 +27,3 @@ pub mod sound_picker; pub mod graphic_picker; pub mod database_modal; - -pub mod event_graphic_picker; diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index b74fc43a..7b2f0320 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -24,7 +24,10 @@ use egui::Widget; use luminol_core::prelude::*; -use luminol_modals::{database_modal, event_graphic_picker}; +use luminol_modals::{ + database_modal::{SwitchModal, VariableModal}, + graphic_picker::event::Modal as GraphicPicker, +}; /// The event editor window. pub struct Window { @@ -32,10 +35,10 @@ pub struct Window { event: rpg::Event, selected_page: usize, - switch_1_modal: database_modal::SwitchModal, - switch_2_modal: database_modal::SwitchModal, - variable_modal: database_modal::VariableModal, - graphic_modal: event_graphic_picker::Modal, + switch_1_modal: SwitchModal, + switch_2_modal: SwitchModal, + variable_modal: VariableModal, + graphic_modal: GraphicPicker, } impl Window { @@ -49,7 +52,7 @@ impl Window { let id_source = egui::Id::new("luminol_event_edit") .with(event.id) .with(map_id); - let graphic_modal = event_graphic_picker::Modal::new( + let graphic_modal = GraphicPicker::new( update_state, &event.pages[0].graphic, tileset_id, @@ -60,9 +63,9 @@ impl Window { event, selected_page: 0, - switch_1_modal: database_modal::Modal::new(id_source.with("switch_1_modal")), - switch_2_modal: database_modal::Modal::new(id_source.with("switch_2_modal")), - variable_modal: database_modal::Modal::new(id_source.with("variable_modal")), + switch_1_modal: SwitchModal::new(id_source.with("switch_1_modal")), + switch_2_modal: SwitchModal::new(id_source.with("switch_2_modal")), + variable_modal: VariableModal::new(id_source.with("variable_modal")), graphic_modal, } } From da7269b0ccec6b9eed0d8319ef4ae106b23153ae Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 06:47:07 -0700 Subject: [PATCH 60/67] Deduplicate more things --- crates/modals/src/graphic_picker/event.rs | 132 ++++++---------------- crates/modals/src/graphic_picker/mod.rs | 6 +- 2 files changed, 35 insertions(+), 103 deletions(-) diff --git a/crates/modals/src/graphic_picker/event.rs b/crates/modals/src/graphic_picker/event.rs index 3a722280..19acd54a 100644 --- a/crates/modals/src/graphic_picker/event.rs +++ b/crates/modals/src/graphic_picker/event.rs @@ -27,14 +27,15 @@ use egui::Widget; use luminol_components::UiExt; use luminol_core::prelude::*; +use super::{ButtonSprite, Entry, PreviewSprite}; + pub struct Modal { state: State, id_source: egui::Id, tileset_id: usize, - button_viewport: Viewport, - button_sprite: Option, + button_sprite: Option, } enum State { @@ -52,18 +53,6 @@ enum State { }, } -#[derive(PartialEq, PartialOrd, Eq, Ord, Clone)] -struct Entry { - path: camino::Utf8PathBuf, - invalid: bool, -} - -struct PreviewSprite { - sprite: Sprite, - sprite_size: egui::Vec2, - viewport: Viewport, -} - #[derive(Default)] enum Selected { #[default] @@ -90,15 +79,20 @@ impl Modal { ) -> Self { let atlas = update_state.graphics.atlas_loader.get_expect(tileset_id); // atlas should be loaded by this point - let button_viewport = Viewport::new(&update_state.graphics, Default::default()); + let viewport = Viewport::new(&update_state.graphics, Default::default()); let button_sprite = Event::new_standalone( &update_state.graphics, update_state.filesystem, - &button_viewport, + &viewport, graphic, &atlas, ) - .unwrap(); // FIXME + .unwrap() // FIXME + .map(|sprite| ButtonSprite { + sprite: sprite.sprite, + sprite_size: sprite.sprite_size, + viewport, + }); Self { state: State::Closed, @@ -106,7 +100,6 @@ impl Modal { tileset_id, - button_viewport, button_sprite, } } @@ -122,28 +115,14 @@ impl luminol_core::Modal for Modal { ) -> impl egui::Widget + 'm { move |ui: &mut egui::Ui| { let desired_size = egui::vec2(64., 96.) + ui.spacing().button_padding * 2.; - let (rect, mut response) = ui.allocate_at_least(desired_size, egui::Sense::click()); - let is_open = matches!(self.state, State::Open { .. }); - let visuals = ui.style().interact_selectable(&response, is_open); - let rect = rect.expand(visuals.expansion); - ui.painter() - .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); - - if let Some(sprite) = &mut self.button_sprite { - let translation = (desired_size - sprite.sprite_size) / 2.; - self.button_viewport.set( - &update_state.graphics.render_state, - glam::vec2(desired_size.x, desired_size.y), - glam::vec2(translation.x, translation.y), - glam::Vec2::ONE, - ); - let callback = luminol_egui_wgpu::Callback::new_paint_callback( - response.rect, - Painter::new(sprite.prepare(&update_state.graphics)), - ); - ui.painter().add(callback); - } + let mut response = ButtonSprite::ui( + self.button_sprite.as_mut(), + ui, + update_state, + is_open, + desired_size, + ); if response.clicked() && !is_open { let selected = if let Some(tile_id) = data.tile_id { @@ -183,25 +162,7 @@ impl luminol_core::Modal for Modal { Selected::None }; - // FIXME error handling - let mut entries: Vec<_> = update_state - .filesystem - .read_dir("Graphics/Characters") - .unwrap() - .into_iter() - .map(|m| { - let path = m - .path - .strip_prefix("Graphics/Characters") - .unwrap_or(&m.path) - .with_extension(""); - Entry { - path, - invalid: false, - } - }) - .collect(); - entries.sort_unstable(); + let entries = Entry::load(update_state, "Graphics/Characters".into()); self.state = State::Open { filtered_entries: entries.clone(), @@ -234,14 +195,20 @@ impl Modal { .atlas_loader .get_expect(self.tileset_id); // atlas should be loaded by this point + let viewport = Viewport::new(&update_state.graphics, Default::default()); self.button_sprite = Event::new_standalone( &update_state.graphics, update_state.filesystem, - &self.button_viewport, + &viewport, graphic, &atlas, ) - .unwrap(); // FIXME + .unwrap() // FIXME + .map(|sprite| ButtonSprite { + sprite: sprite.sprite, + sprite_size: sprite.sprite_size, + viewport, + }); } fn load_tilepicker( @@ -345,16 +312,7 @@ impl Modal { .hint_text("Search 🔎") .show(ui); if out.response.changed() { - let matcher = fuzzy_matcher::skim::SkimMatcherV2::default(); - *filtered_entries = entries - .iter() - .filter(|entry| { - matcher - .fuzzy(entry.path.as_str(), search_text, false) - .is_some() - }) - .cloned() - .collect(); + *filtered_entries = Entry::filter(entries, search_text); } ui.separator(); @@ -465,38 +423,14 @@ impl Modal { match selected { Selected::None => {} Selected::Graphic { direction, pattern, sprite, .. } => { - let (canvas_rect, response) = ui.allocate_exact_size( - sprite.sprite_size, - egui::Sense::click(), - ); - - let absolute_scroll_rect = ui - .ctx() - .screen_rect() - .intersect(viewport.translate(canvas_rect.min.to_vec2())); - let scroll_rect = absolute_scroll_rect.translate(-canvas_rect.min.to_vec2()); - sprite.sprite.transform.set_position( - &update_state.graphics.render_state, - glam::vec2(-scroll_rect.left(), -scroll_rect.top()), - ); - - sprite.viewport.set( - &update_state.graphics.render_state, - glam::vec2(absolute_scroll_rect.width(), absolute_scroll_rect.height()), - glam::Vec2::ZERO, - glam::Vec2::ONE, - ); - - let painter = Painter::new(sprite.sprite.prepare(&update_state.graphics)); - ui.painter() - .add(luminol_egui_wgpu::Callback::new_paint_callback( - absolute_scroll_rect, - painter, - )); + let response = sprite.ui(ui, viewport, update_state); let ch = sprite.sprite_size.y / 4.; let cw = sprite.sprite_size.x / 4.; - let rect = egui::Rect::from_min_size(egui::pos2(*pattern as f32 * cw, (*direction as f32 - 2.) * ch / 2.), egui::vec2(cw, ch)).translate(canvas_rect.min.to_vec2()); + + let min = egui::pos2(*pattern as f32 * cw, (*direction as f32 - 2.) * ch / 2.); + let size = egui::vec2(cw, ch); + let rect = egui::Rect::from_min_size(min, size).translate(response.rect.min.to_vec2()); ui.painter().rect_stroke(rect, 5.0, egui::Stroke::new(1.0, egui::Color32::WHITE)); if response.clicked() { diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index db85c3d5..b220809a 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -164,10 +164,8 @@ impl PreviewSprite { viewport: egui::Rect, update_state: &UpdateState<'_>, ) -> egui::Response { - let (canvas_rect, response) = ui.allocate_exact_size( - self.sprite_size, - egui::Sense::focusable_noninteractive(), // FIXME screen reader hints - ); + let (canvas_rect, response) = + ui.allocate_exact_size(self.sprite_size, egui::Sense::click()); let absolute_scroll_rect = ui .ctx() From af1a4475acabffb2e849cedf0944c3ce5aca2c64 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 07:02:05 -0700 Subject: [PATCH 61/67] Hack in the enemy battler picker --- crates/modals/src/graphic_picker/hue.rs | 19 ++++++++ crates/ui/src/windows/enemies.rs | 59 ++++++++++++++++++++----- crates/ui/src/windows/items.rs | 2 +- src/app/top_bar.rs | 2 +- 4 files changed, 70 insertions(+), 12 deletions(-) diff --git a/crates/modals/src/graphic_picker/hue.rs b/crates/modals/src/graphic_picker/hue.rs index 8ee4e68d..a56514df 100644 --- a/crates/modals/src/graphic_picker/hue.rs +++ b/crates/modals/src/graphic_picker/hue.rs @@ -273,6 +273,25 @@ impl Modal { }); }); + egui::TopBottomPanel::top(self.id_source.with("top")).show_inside(ui, |ui| { + ui.add_space(1.0); // pad out the top + ui.horizontal(|ui| { + ui.label("Hue"); + if ui.add(egui::Slider::new(hue, 0..=360)).changed() { + match selected { + Selected::Entry { sprite, .. } => { + sprite + .sprite + .graphic + .set_hue(&update_state.graphics.render_state, *hue); + } + Selected::None => {} + } + } + }); + ui.add_space(1.0); // pad out the bottom + }); + egui::TopBottomPanel::bottom(self.id_source.with("bottom")).show_inside(ui, |ui| { ui.add_space(ui.style().spacing.item_spacing.y); luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); diff --git a/crates/ui/src/windows/enemies.rs b/crates/ui/src/windows/enemies.rs index c8091b15..070ea7a7 100644 --- a/crates/ui/src/windows/enemies.rs +++ b/crates/ui/src/windows/enemies.rs @@ -23,6 +23,8 @@ // Program grant you additional permission to convey the resulting work. use luminol_components::UiExt; +use luminol_core::Modal; +use luminol_modals::graphic_picker::hue::Modal as GraphicPicker; #[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] #[derive(strum::Display, strum::EnumIter)] @@ -35,18 +37,36 @@ pub enum TreasureType { Armor, } -#[derive(Default)] pub struct Window { selected_enemy_name: Option, previous_enemy: Option, + graphic_picker: GraphicPicker, + collapsing_view: luminol_components::CollapsingView, view: luminol_components::DatabaseView, } impl Window { - pub fn new() -> Self { - Default::default() + pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { + let enemies = update_state.data.enemies(); + let enemy = &enemies.data[0]; + Self { + selected_enemy_name: None, + previous_enemy: None, + + graphic_picker: GraphicPicker::new( + update_state, + "Graphics/Battlers".into(), + enemy.battler_name.as_deref(), + enemy.battler_hue, + egui::vec2(196., 256.), + "enemy_battler_picker", + ), + + collapsing_view: luminol_components::CollapsingView::new(), + view: luminol_components::DatabaseView::new(), + } } fn show_action_header( @@ -279,13 +299,32 @@ impl luminol_core::Window for Window { self.selected_enemy_name = Some(enemy.name.clone()); ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut enemy.name) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.horizontal(|ui| { + modified |= ui + .add(luminol_components::Field::new( + "Graphic", + self.graphic_picker.button( + (&mut enemy.battler_name, &mut enemy.battler_hue), + update_state, + ), + )) + .changed(); + if self.previous_enemy != Some(enemy.id) { + // avoid desyncs by resetting the modal if the item has changed + self.graphic_picker.reset( + update_state, + (&mut enemy.battler_name, &mut enemy.battler_hue), + ); + } + + modified |= ui + .add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut enemy.name) + .desired_width(f32::INFINITY), + )) + .changed(); + }); }); ui.with_padded_stripe(true, |ui| { diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index a8364d2a..c5fac9fe 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -49,7 +49,7 @@ impl Window { menu_se_picker: SoundPicker::new(luminol_audio::Source::SE, "item_menu_se_picker"), graphic_picker: GraphicPicker::new( update_state, - camino::Utf8PathBuf::from("Graphics/Icons"), + "Graphics/Icons".into(), item.icon_name.as_deref(), egui::vec2(32., 32.), "item_icon_picker", diff --git a/src/app/top_bar.rs b/src/app/top_bar.rs index e3a4027d..964006f2 100644 --- a/src/app/top_bar.rs +++ b/src/app/top_bar.rs @@ -255,7 +255,7 @@ impl TopBar { if ui.button("Enemies").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::enemies::Window::new()); + .add_window(luminol_ui::windows::enemies::Window::new(update_state)); } ui.add_enabled_ui(false, |ui| { From ee43ffd6d16981eb38cf9cbdc36aa92ed4b4c450 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Tue, 2 Jul 2024 07:40:50 -0700 Subject: [PATCH 62/67] add hacky actor graphic picker --- crates/graphics/src/primitives/sprite/mod.rs | 10 + crates/modals/src/graphic_picker/actor.rs | 345 +++++++++++++++++++ crates/modals/src/graphic_picker/mod.rs | 1 + crates/ui/src/windows/actors.rs | 61 +++- src/app/top_bar.rs | 2 +- 5 files changed, 408 insertions(+), 11 deletions(-) create mode 100644 crates/modals/src/graphic_picker/actor.rs diff --git a/crates/graphics/src/primitives/sprite/mod.rs b/crates/graphics/src/primitives/sprite/mod.rs index c770a8b8..6300b4b0 100644 --- a/crates/graphics/src/primitives/sprite/mod.rs +++ b/crates/graphics/src/primitives/sprite/mod.rs @@ -85,6 +85,16 @@ impl Sprite { ) -> Self { let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2()); let quad = Quad::new(rect, rect); + Self::basic_hue_quad(graphics_state, hue, quad, texture, viewport) + } + + pub fn basic_hue_quad( + graphics_state: &GraphicsState, + hue: i32, + quad: Quad, + texture: &Texture, + viewport: &Viewport, + ) -> Self { Self::new( graphics_state, quad, diff --git a/crates/modals/src/graphic_picker/actor.rs b/crates/modals/src/graphic_picker/actor.rs new file mode 100644 index 00000000..d49be4eb --- /dev/null +++ b/crates/modals/src/graphic_picker/actor.rs @@ -0,0 +1,345 @@ +// Copyright (C) 2023 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 color_eyre::eyre::Context; +use luminol_components::UiExt; +use luminol_core::prelude::*; + +use super::{ButtonSprite, Entry, PreviewSprite, Selected}; + +pub struct Modal { + state: State, + id_source: egui::Id, + + button_size: egui::Vec2, + directory: camino::Utf8PathBuf, // do we make this &'static Utf8Path? + + button_sprite: Option, +} + +enum State { + Closed, + Open { + entries: Vec, + filtered_entries: Vec, + search_text: String, + + hue: i32, + + selected: Selected, + }, +} + +impl Modal { + pub fn new( + update_state: &UpdateState<'_>, + directory: camino::Utf8PathBuf, + path: Option<&camino::Utf8Path>, + hue: i32, + button_size: egui::Vec2, + id_source: impl Into, + ) -> Self { + let button_sprite = path.map(|path| { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, &directory, path) + .unwrap(); // FIXME + + let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2() / 4.); + let quad = Quad::new(rect, rect); + + let button_viewport = Viewport::new(&update_state.graphics, Default::default()); + let sprite = Sprite::basic_hue_quad( + &update_state.graphics, + hue, + quad, + &texture, + &button_viewport, + ); + ButtonSprite { + sprite, + sprite_size: texture.size_vec2() / 4., + viewport: button_viewport, + } + }); + + Self { + state: State::Closed, + id_source: id_source.into(), + button_size, + directory, + button_sprite, + } + } +} + +impl luminol_core::Modal for Modal { + type Data<'m> = (&'m mut Option, &'m mut i32); + + fn button<'m>( + &'m mut self, + data: Self::Data<'m>, + update_state: &'m mut luminol_core::UpdateState<'_>, + ) -> impl egui::Widget + 'm { + |ui: &mut egui::Ui| { + let desired_size = self.button_size + ui.spacing().button_padding * 2.0; + let is_open = matches!(self.state, State::Open { .. }); + let mut response = ButtonSprite::ui( + self.button_sprite.as_mut(), + ui, + update_state, + is_open, + desired_size, + ); + + if response.clicked() && !is_open { + let selected = match data.0.clone() { + Some(path) => { + // FIXME error handling + let sprite = + Self::load_preview_sprite(update_state, &self.directory, &path) + .unwrap(); + Selected::Entry { path, sprite } + } + None => Selected::None, + }; + + let entries = Entry::load(update_state, &self.directory); + + self.state = State::Open { + filtered_entries: entries.clone(), + entries, + hue: *data.1, + search_text: String::new(), + selected, + }; + } + if self.show_window(update_state, ui.ctx(), data) { + response.mark_changed(); + } + + response + } + } + + fn reset(&mut self, update_state: &mut luminol_core::UpdateState<'_>, data: Self::Data<'_>) { + self.update_graphic(update_state, data); // we need to update the button sprite to prevent desyncs + self.state = State::Closed; + } +} + +impl Modal { + fn load_preview_sprite( + update_state: &luminol_core::UpdateState<'_>, + directory: &camino::Utf8Path, + path: &camino::Utf8Path, + ) -> color_eyre::Result { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, directory, path) + .wrap_err("While loading a preview sprite")?; + + Ok(Self::create_preview_sprite_from_texture( + update_state, + &texture, + )) + } + + fn create_preview_sprite_from_texture( + update_state: &luminol_core::UpdateState<'_>, + texture: &Texture, + ) -> PreviewSprite { + let viewport = Viewport::new( + &update_state.graphics, + glam::vec2(texture.width() as f32, texture.height() as f32), + ); + + let sprite = Sprite::basic(&update_state.graphics, texture, &viewport); + PreviewSprite { + sprite, + sprite_size: texture.size_vec2(), + viewport, + } + } + + fn update_graphic( + &mut self, + update_state: &UpdateState<'_>, + data: (&mut Option, &mut i32), + ) { + self.button_sprite = data.0.as_ref().map(|path| { + let texture = update_state + .graphics + .texture_loader + .load_now_dir(update_state.filesystem, &self.directory, path) + .unwrap(); // FIXME + + let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, texture.size_vec2() / 4.); + let quad = Quad::new(rect, rect); + + let button_viewport = Viewport::new(&update_state.graphics, Default::default()); + let sprite = Sprite::basic_hue_quad( + &update_state.graphics, + *data.1, + quad, + &texture, + &button_viewport, + ); + ButtonSprite { + sprite, + sprite_size: texture.size_vec2() / 4., + viewport: button_viewport, + } + }); + } + + fn show_window( + &mut self, + update_state: &mut luminol_core::UpdateState<'_>, + ctx: &egui::Context, + data: (&mut Option, &mut i32), + ) -> bool { + let mut win_open = true; + let mut keep_open = true; + let mut needs_save = false; + + let State::Open { + entries, + filtered_entries, + hue, + search_text, + selected, + } = &mut self.state + else { + return false; + }; + + egui::Window::new("Graphic Picker") + .resizable(true) + .open(&mut win_open) + .id(self.id_source.with("window")) + .show(ctx, |ui| { + egui::SidePanel::left(self.id_source.with("sidebar")).show_inside(ui, |ui| { + let out = egui::TextEdit::singleline(search_text) + .hint_text("Search 🔎") + .show(ui); + if out.response.changed() { + *filtered_entries = Entry::filter(entries, search_text); + } + + ui.separator(); + + // Get row height. + let row_height = ui.text_style_height(&egui::TextStyle::Body); // i do not trust this + // FIXME scroll to selected on first open + ui.with_cross_justify(|ui| { + egui::ScrollArea::vertical() + .auto_shrink([false, true]) + .show_rows( + ui, + row_height, + filtered_entries.len() + 1, + |ui, mut rows| { + if rows.contains(&0) { + let res = ui.selectable_label( + matches!(selected, Selected::None), + "(None)", + ); + if res.clicked() && !matches!(selected, Selected::None) { + *selected = Selected::None; + } + } + + // subtract 2 to account for (None) + rows.start = rows.start.saturating_sub(1); + rows.end = rows.end.saturating_sub(1); + + Entry::ui(filtered_entries, ui, rows, selected, |path| { + Self::load_preview_sprite( + update_state, + &self.directory, + path, + ) + .unwrap() + }) + }, + ); + }); + }); + + egui::TopBottomPanel::top(self.id_source.with("top")).show_inside(ui, |ui| { + ui.add_space(1.0); // pad out the top + ui.horizontal(|ui| { + ui.label("Hue"); + if ui.add(egui::Slider::new(hue, 0..=360)).changed() { + match selected { + Selected::Entry { sprite, .. } => { + sprite + .sprite + .graphic + .set_hue(&update_state.graphics.render_state, *hue); + } + Selected::None => {} + } + } + }); + ui.add_space(1.0); // pad out the bottom + }); + + egui::TopBottomPanel::bottom(self.id_source.with("bottom")).show_inside(ui, |ui| { + ui.add_space(ui.style().spacing.item_spacing.y); + luminol_components::close_options_ui(ui, &mut keep_open, &mut needs_save); + }); + + egui::CentralPanel::default().show_inside(ui, |ui| { + egui::ScrollArea::both() + .auto_shrink([false, false]) + .show_viewport(ui, |ui, viewport| match selected { + Selected::None => {} + Selected::Entry { sprite, .. } => { + sprite.ui(ui, viewport, update_state); + } + }); + }); + }); + + if needs_save { + match selected { + Selected::None => *data.0 = None, + Selected::Entry { path, .. } => *data.0 = Some(path.clone()), + } + *data.1 = *hue; + + self.update_graphic(update_state, data); + } + + if !(win_open && keep_open) { + self.state = State::Closed; + } + + needs_save + } +} diff --git a/crates/modals/src/graphic_picker/mod.rs b/crates/modals/src/graphic_picker/mod.rs index b220809a..d0507bda 100644 --- a/crates/modals/src/graphic_picker/mod.rs +++ b/crates/modals/src/graphic_picker/mod.rs @@ -25,6 +25,7 @@ use luminol_components::UiExt; use luminol_core::prelude::*; +pub mod actor; pub mod basic; pub mod event; pub mod hue; diff --git a/crates/ui/src/windows/actors.rs b/crates/ui/src/windows/actors.rs index 7198d66d..6301dff2 100644 --- a/crates/ui/src/windows/actors.rs +++ b/crates/ui/src/windows/actors.rs @@ -25,13 +25,16 @@ use itertools::Itertools; use luminol_components::UiExt; +use luminol_core::Modal; use luminol_data::rpg::armor::Kind; +use luminol_modals::graphic_picker::actor::Modal as GraphicPicker; -#[derive(Default)] pub struct Window { selected_actor_name: Option, previous_actor: Option, + graphic_picker: GraphicPicker, + exp_view_is_total: bool, exp_view_is_depersisted: bool, @@ -39,8 +42,27 @@ pub struct Window { } impl Window { - pub fn new() -> Self { - Default::default() + pub fn new(update_state: &luminol_core::UpdateState<'_>) -> Self { + let actors = update_state.data.actors(); + let actor = &actors.data[0]; + Self { + selected_actor_name: None, + previous_actor: None, + + graphic_picker: GraphicPicker::new( + update_state, + "Graphics/Characters".into(), + actor.character_name.as_deref(), + actor.character_hue, + egui::vec2(64., 96.), + "actor_graphic_picker", + ), + + exp_view_is_depersisted: false, + exp_view_is_total: false, + + view: luminol_components::DatabaseView::new(), + } } } @@ -251,13 +273,32 @@ impl luminol_core::Window for Window { self.selected_actor_name = Some(actor.name.clone()); ui.with_padded_stripe(false, |ui| { - modified |= ui - .add(luminol_components::Field::new( - "Name", - egui::TextEdit::singleline(&mut actor.name) - .desired_width(f32::INFINITY), - )) - .changed(); + ui.horizontal(|ui| { + modified |= ui + .add(luminol_components::Field::new( + "Icon", + self.graphic_picker.button( + (&mut actor.character_name, &mut actor.character_hue), + update_state, + ), + )) + .changed(); + if self.previous_actor != Some(actor.id) { + // avoid desyncs by resetting the modal if the item has changed + self.graphic_picker.reset( + update_state, + (&mut actor.character_name, &mut actor.character_hue), + ); + } + + modified |= ui + .add(luminol_components::Field::new( + "Name", + egui::TextEdit::singleline(&mut actor.name) + .desired_width(f32::INFINITY), + )) + .changed(); + }) }); ui.with_padded_stripe(true, |ui| { diff --git a/src/app/top_bar.rs b/src/app/top_bar.rs index 964006f2..911bf437 100644 --- a/src/app/top_bar.rs +++ b/src/app/top_bar.rs @@ -243,7 +243,7 @@ impl TopBar { if ui.button("Actors").clicked() { update_state .edit_windows - .add_window(luminol_ui::windows::actors::Window::new()); + .add_window(luminol_ui::windows::actors::Window::new(update_state)); } if ui.button("Classes").clicked() { From 83616c2b418590381b0e5456f59c25b7f6e4e96b Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Wed, 3 Jul 2024 09:16:21 -0700 Subject: [PATCH 63/67] Directly* edit events --- crates/components/src/map_view.rs | 4 +- crates/data/src/option_vec.rs | 9 ++ crates/data/src/shared/event.rs | 2 +- crates/ui/src/tabs/map/mod.rs | 2 +- crates/ui/src/tabs/map/util.rs | 5 +- crates/ui/src/windows/event_edit.rs | 125 +++++++++++++++++----------- 6 files changed, 93 insertions(+), 54 deletions(-) diff --git a/crates/components/src/map_view.rs b/crates/components/src/map_view.rs index 4cc402df..4f424f95 100644 --- a/crates/components/src/map_view.rs +++ b/crates/components/src/map_view.rs @@ -371,8 +371,8 @@ impl MapView { let mut selected_event_rect = None; for (_, event) in map.events.iter() { - if event.extra_data.modified.get() { - event.extra_data.modified.set(false); + if event.extra_data.graphic_modified.get() { + event.extra_data.graphic_modified.set(false); let sprite = luminol_graphics::Event::new_map( &update_state.graphics, update_state.filesystem, diff --git a/crates/data/src/option_vec.rs b/crates/data/src/option_vec.rs index 92389117..13a71949 100644 --- a/crates/data/src/option_vec.rs +++ b/crates/data/src/option_vec.rs @@ -112,6 +112,15 @@ impl OptionVec { } } + pub fn option_remove(&mut self, index: usize) -> Option { + if index >= self.len() { + None + } else { + self.num_values -= 1; + self.vec[index].take() + } + } + /// Remove the element at the given index and return it. /// If the OptionVec is not big enough to contain this index, this will panic. /// If there isn't an element at that index, this will panic. diff --git a/crates/data/src/shared/event.rs b/crates/data/src/shared/event.rs index b641afc5..1db0c8b8 100644 --- a/crates/data/src/shared/event.rs +++ b/crates/data/src/shared/event.rs @@ -40,7 +40,7 @@ pub struct Event { pub struct EventExtraData { /// Whether or not the event editor for this event is open pub is_editor_open: bool, - pub modified: std::cell::Cell, + pub graphic_modified: std::cell::Cell, } impl Event { diff --git a/crates/ui/src/tabs/map/mod.rs b/crates/ui/src/tabs/map/mod.rs index d6439f41..5d567619 100644 --- a/crates/ui/src/tabs/map/mod.rs +++ b/crates/ui/src/tabs/map/mod.rs @@ -480,7 +480,7 @@ impl luminol_core::Tab for Tab { let event = map.events[selected_event_id].clone(); self.event_windows.add_window(event_edit::Window::new( update_state, - event, + &event, self.id, map.tileset_id, )); diff --git a/crates/ui/src/tabs/map/util.rs b/crates/ui/src/tabs/map/util.rs index 70802fcb..0866b961 100644 --- a/crates/ui/src/tabs/map/util.rs +++ b/crates/ui/src/tabs/map/util.rs @@ -236,15 +236,16 @@ impl super::Tab { self.view.cursor_pos.y as i32, new_event_id, ); - map.events.insert(new_event_id, event.clone()); self.event_windows .add_window(crate::windows::event_edit::Window::new( update_state, - event, + &event, self.id, map.tileset_id, )); + + map.events.insert(new_event_id, event); Some(new_event_id) } diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index 7b2f0320..9cfcb3db 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -32,7 +32,7 @@ use luminol_modals::{ /// The event editor window. pub struct Window { map_id: usize, - event: rpg::Event, + event_id: usize, selected_page: usize, switch_1_modal: SwitchModal, @@ -45,7 +45,7 @@ impl Window { /// Create a new event editor. pub fn new( update_state: &UpdateState<'_>, - event: rpg::Event, + event: &rpg::Event, map_id: usize, tileset_id: usize, ) -> Self { @@ -60,7 +60,7 @@ impl Window { ); Self { map_id, - event, + event_id: event.id, selected_page: 0, switch_1_modal: SwitchModal::new(id_source.with("switch_1_modal")), @@ -73,13 +73,13 @@ impl Window { impl luminol_core::Window for Window { fn name(&self) -> String { - format!("Event '{}' ID {}", self.event.name, self.event.id) + format!("Event 'TODO' ID {}", self.event_id) } fn id(&self) -> egui::Id { egui::Id::new("luminol_event_edit") .with(self.map_id) - .with(self.event.id) + .with(self.event_id) } fn show( @@ -88,24 +88,34 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - let mut win_open = true; - let mut needs_save = false; + // to avoid borrowing issues, we temporarily remove the event from the map. + // this is a pretty cheap operation because it's Option::take. + let mut map = update_state.data.get_map(self.map_id); + let Some(mut event) = map.events.option_remove(self.event_id) else { + *open = false; + return; + }; + drop(map); + + let mut modified = false; + let mut graphic_modified = false; egui::Window::new(self.name()) - .open(&mut win_open) + .open(open) .id(self.id()) .show(ctx, |ui| { let id_source = self.id(); let previous_page = self.selected_page; + egui::TopBottomPanel::top(id_source.with("top_panel")).show_inside(ui, |ui| { ui.add_space(1.0); // pad the top of the window ui.horizontal(|ui| { ui.label("Name: "); - ui.text_edit_singleline(&mut self.event.name); + ui.text_edit_singleline(&mut event.name); }); ui.horizontal(|ui| { ui.label("Page: "); - for i in 0..self.event.pages.len() { + for i in 0..event.pages.len() { ui.selectable_value(&mut self.selected_page, i, format!("{}", i + 1)); } @@ -113,25 +123,28 @@ impl luminol_core::Window for Window { .button(egui::RichText::new("Add").color(egui::Color32::LIGHT_GREEN)) .clicked() { - self.event.pages.push(rpg::EventPage::default()); - self.selected_page = self.event.pages.len() - 1; + modified |= true; + event.pages.push(rpg::EventPage::default()); + self.selected_page = event.pages.len() - 1; } let button = egui::Button::new( egui::RichText::new("Delete").color(egui::Color32::LIGHT_RED), ); - if ui.add_enabled(self.event.pages.len() > 1, button).clicked() { - self.event.pages.remove(self.selected_page); + if ui.add_enabled(event.pages.len() > 1, button).clicked() { + modified |= true; + event.pages.remove(self.selected_page); self.selected_page = self.selected_page.saturating_sub(1); } if ui.button(egui::RichText::new("Clear")).clicked() { - self.event.pages[self.selected_page] = rpg::EventPage::default(); + modified |= true; + event.pages[self.selected_page] = rpg::EventPage::default(); } }); ui.add_space(1.0); // pad the bottom of the window }); - let page = &mut self.event.pages[self.selected_page]; + let page = &mut event.pages[self.selected_page]; if self.selected_page != previous_page { // reset the modal if we've changed pages self.graphic_modal.reset(update_state, &mut page.graphic); @@ -142,40 +155,44 @@ impl luminol_core::Window for Window { ui.group(|ui| { ui.horizontal(|ui| { ui.checkbox(&mut page.condition.switch1_valid, "Switch"); - ui.add_enabled( + let res = ui.add_enabled( page.condition.switch1_valid, self.switch_1_modal .button(&mut page.condition.switch1_id, update_state), ); + modified |= res.changed(); ui.label("is ON"); }); ui.horizontal(|ui| { ui.checkbox(&mut page.condition.switch2_valid, "Switch"); - ui.add_enabled( + let res = ui.add_enabled( page.condition.switch2_valid, self.switch_2_modal .button(&mut page.condition.switch2_id, update_state), ); + modified |= res.changed(); ui.label("is ON"); }); ui.horizontal(|ui| { ui.checkbox(&mut page.condition.variable_valid, "Variable"); - ui.add_enabled( + let res = ui.add_enabled( page.condition.variable_valid, self.variable_modal .button(&mut page.condition.variable_id, update_state), ); + modified |= res.changed(); ui.label("is"); - ui.add_enabled( + let res = ui.add_enabled( page.condition.variable_valid, egui::DragValue::new(&mut page.condition.variable_value), ); + modified |= res.changed(); ui.label("or above"); }); ui.horizontal(|ui| { ui.checkbox(&mut page.condition.self_switch_valid, "Self Switch"); // TODO add self switch text box (config option) - ui.add_enabled( + let res = ui.add_enabled( // FIXME ensure shrink page.condition.self_switch_valid, luminol_components::EnumMenuButton::new( @@ -183,6 +200,7 @@ impl luminol_core::Window for Window { id_source.with("self_switch_ch"), ), ); + modified |= res.changed(); ui.label("is ON"); // ensure we expand to fit the side panel ui.add_space(ui.available_width()); // cross justify doesn't seem to be able to replace this? @@ -193,9 +211,11 @@ impl luminol_core::Window for Window { ui.vertical(|ui| { ui.label("Graphic"); - self.graphic_modal + graphic_modified = self + .graphic_modal .button(&mut page.graphic, update_state) - .ui(ui); + .ui(ui) + .changed(); }); ui.vertical(|ui| { ui.label("Autonomous Movement"); @@ -203,11 +223,12 @@ impl luminol_core::Window for Window { // FIXME these expand to fit, which is kinda annoying ui.horizontal(|ui| { ui.label("Move Type"); - luminol_components::EnumComboBox::new( + modified |= luminol_components::EnumComboBox::new( id_source.with("move_type"), &mut page.move_type, ) - .ui(ui); + .ui(ui) + .changed(); }); ui.add_enabled( page.move_type == luminol_data::rpg::MoveType::Custom, @@ -215,19 +236,21 @@ impl luminol_core::Window for Window { ); // TODO ui.horizontal(|ui| { ui.label("Move Speed"); - luminol_components::EnumComboBox::new( + modified |= luminol_components::EnumComboBox::new( id_source.with("move_speed"), &mut page.move_speed, ) - .ui(ui); + .ui(ui) + .changed(); }); ui.horizontal(|ui| { ui.label("Move Frequency"); - luminol_components::EnumComboBox::new( + modified |= luminol_components::EnumComboBox::new( id_source.with("move_frequency"), &mut page.move_frequency, ) - .ui(ui); + .ui(ui) + .changed(); }); ui.add_space(ui.available_height()); }); @@ -242,36 +265,42 @@ impl luminol_core::Window for Window { left.label("Options"); left.group(|ui| { ui.style_mut().wrap = Some(false); - ui.checkbox(&mut page.walk_anime, "Move Animation"); - ui.checkbox(&mut page.step_anime, "Stop Animation"); - ui.checkbox(&mut page.direction_fix, "Direction Fix"); - ui.checkbox(&mut page.through, "Through"); - ui.checkbox(&mut page.always_on_top, "Always on Top"); + modified |= ui + .checkbox(&mut page.walk_anime, "Move Animation") + .changed(); + modified |= ui + .checkbox(&mut page.step_anime, "Stop Animation") + .changed(); + modified |= ui + .checkbox(&mut page.direction_fix, "Direction Fix") + .changed(); + modified |= ui.checkbox(&mut page.through, "Through").changed(); + modified |= ui + .checkbox(&mut page.always_on_top, "Always on Top") + .changed(); }); right.label("Trigger"); right.group(|ui| { - luminol_components::EnumRadioList::new(&mut page.trigger).ui(ui); + modified |= luminol_components::EnumRadioList::new(&mut page.trigger) + .ui(ui) + .changed(); }); }); }); - - egui::TopBottomPanel::bottom(id_source.with("bottom_panel")).show_inside( - ui, - |ui| { - ui.add_space(1.0); // it still looks a little squished, even with this. how can we fix this? - luminol_components::close_options_ui(ui, open, &mut needs_save) - }, - ); }); - if needs_save { - self.event.extra_data.modified.set(true); - let mut map = update_state.data.get_map(self.map_id); - map.events.insert(self.event.id, self.event.clone()); // don't like the extra clone, but it's necessary + if graphic_modified { + event.extra_data.graphic_modified.set(true); } - *open &= win_open; + // reinsert the event into the map + let mut map = update_state.data.get_map(self.map_id); + map.events.insert(self.event_id, event); + + if modified { + map.modified = true; + } } fn requires_filesystem(&self) -> bool { From ec84e01c49db09daf4c2adfbd9fc2324d400361b Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Wed, 3 Jul 2024 09:24:23 -0700 Subject: [PATCH 64/67] Remove Window::name It was just technical debt, back from when Window deduplication was by Window::name instead of Window::id --- crates/core/src/window.rs | 5 -- crates/ui/src/lib.rs | 8 --- crates/ui/src/windows/about.rs | 4 -- crates/ui/src/windows/actors.rs | 16 ++--- crates/ui/src/windows/appearance.rs | 70 +++++++++---------- crates/ui/src/windows/armor.rs | 16 ++--- crates/ui/src/windows/classes.rs | 16 ++--- crates/ui/src/windows/common_event_edit.rs | 16 ++--- crates/ui/src/windows/config_window.rs | 52 +++++++------- crates/ui/src/windows/console.rs | 6 +- crates/ui/src/windows/enemies.rs | 16 ++--- crates/ui/src/windows/event_edit.rs | 6 +- crates/ui/src/windows/global_config_window.rs | 6 +- crates/ui/src/windows/items.rs | 16 ++--- crates/ui/src/windows/misc.rs | 18 +---- crates/ui/src/windows/new_project.rs | 6 +- crates/ui/src/windows/reporter.rs | 6 +- crates/ui/src/windows/script_edit.rs | 16 ++--- crates/ui/src/windows/skills.rs | 16 ++--- crates/ui/src/windows/states.rs | 16 ++--- crates/ui/src/windows/weapons.rs | 16 ++--- 21 files changed, 137 insertions(+), 210 deletions(-) diff --git a/crates/core/src/window.rs b/crates/core/src/window.rs index 324d0960..7c3157c8 100644 --- a/crates/core/src/window.rs +++ b/crates/core/src/window.rs @@ -154,11 +154,6 @@ pub trait Window { update_state: &mut crate::UpdateState<'_>, ); - /// Optionally used as the title of the window. - fn name(&self) -> String { - "Untitled Window".to_string() - } - /// Required to prevent duplication. fn id(&self) -> egui::Id; diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 7f5cdcf2..f92a976d 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -125,14 +125,6 @@ macro_rules! window_enum { } } - fn name(&self) -> String { - match self { - $( - Self::$variant(v) => v.name(), - )* - } - } - fn id(&self) -> egui::Id { match self { $( diff --git a/crates/ui/src/windows/about.rs b/crates/ui/src/windows/about.rs index 921ac72d..3e043d84 100644 --- a/crates/ui/src/windows/about.rs +++ b/crates/ui/src/windows/about.rs @@ -42,10 +42,6 @@ impl Default for Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - "About".to_string() - } - fn id(&self) -> egui::Id { egui::Id::new("About Luminol") } diff --git a/crates/ui/src/windows/actors.rs b/crates/ui/src/windows/actors.rs index 6301dff2..8e30e6be 100644 --- a/crates/ui/src/windows/actors.rs +++ b/crates/ui/src/windows/actors.rs @@ -222,14 +222,6 @@ fn draw_exp(ui: &mut egui::Ui, actor: &luminol_data::rpg::Actor, total: &mut boo } impl luminol_core::Window for Window { - fn name(&self) -> String { - if let Some(name) = &self.selected_actor_name { - format!("Editing actor {:?}", name) - } else { - "Actor Editor".into() - } - } - fn id(&self) -> egui::Id { egui::Id::new("actor_editor") } @@ -257,7 +249,13 @@ impl luminol_core::Window for Window { self.selected_actor_name = None; - let response = egui::Window::new(self.name()) + let name = if let Some(name) = &self.selected_actor_name { + format!("Editing actor {:?}", name) + } else { + "Actor Editor".into() + }; + + let response = egui::Window::new(name) .id(self.id()) .default_width(500.) .open(open) diff --git a/crates/ui/src/windows/appearance.rs b/crates/ui/src/windows/appearance.rs index 4743bba5..59d65d8c 100644 --- a/crates/ui/src/windows/appearance.rs +++ b/crates/ui/src/windows/appearance.rs @@ -35,35 +35,33 @@ impl luminol_core::Window for Window { egui::Id::new("luminol_appearance_window") } - fn name(&self) -> String { - "Luminol Appearance".to_string() - } - fn show( &mut self, ctx: &egui::Context, open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - egui::Window::new(self.name()).open(open).show(ctx, |ui| { - // Or these together so if one OR the other is true the window shows. - self.egui_settings_open = - ui.button("Egui Settings").clicked() || self.egui_settings_open; + egui::Window::new("Luminol Appearance") + .open(open) + .show(ctx, |ui| { + // Or these together so if one OR the other is true the window shows. + self.egui_settings_open = + ui.button("Egui Settings").clicked() || self.egui_settings_open; - ui.menu_button("Code Theme", |ui| { - for t in luminol_config::SyntectTheme::iter() { - ui.radio_value( - &mut update_state.global_config.theme.syntect_theme, - t, - t.to_string(), - ); - } + ui.menu_button("Code Theme", |ui| { + for t in luminol_config::SyntectTheme::iter() { + ui.radio_value( + &mut update_state.global_config.theme.syntect_theme, + t, + t.to_string(), + ); + } - ui.label("Code sample"); - ui.label(luminol_components::syntax_highlighting::highlight( - ui.ctx(), - update_state.global_config.theme, - r#" + ui.label("Code sample"); + ui.label(luminol_components::syntax_highlighting::highlight( + ui.ctx(), + update_state.global_config.theme, + r#" class Foo < Array end def bar(baz) @@ -71,21 +69,21 @@ impl luminol_core::Window for Window { print 1, 2.0 puts [0x3, :4, '5'] "#, - "rb", - )); - }); + "rb", + )); + }); - if ui - .button("Clear Loaded Textures") - .on_hover_text( - "You may need to reopen maps/windows for any changes to take effect.", - ) - .clicked() - { - update_state.graphics.texture_loader.clear(); - update_state.graphics.atlas_loader.clear(); - update_state.bytes_loader.forget_all(); - } - }); + if ui + .button("Clear Loaded Textures") + .on_hover_text( + "You may need to reopen maps/windows for any changes to take effect.", + ) + .clicked() + { + update_state.graphics.texture_loader.clear(); + update_state.graphics.atlas_loader.clear(); + update_state.bytes_loader.forget_all(); + } + }); } } diff --git a/crates/ui/src/windows/armor.rs b/crates/ui/src/windows/armor.rs index d4febb8f..cf73a50b 100644 --- a/crates/ui/src/windows/armor.rs +++ b/crates/ui/src/windows/armor.rs @@ -39,14 +39,6 @@ impl Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - if let Some(name) = &self.selected_armor_name { - format!("Editing armor {:?}", name) - } else { - "Armor Editor".into() - } - } - fn id(&self) -> egui::Id { egui::Id::new("armor_editor") } @@ -70,7 +62,13 @@ impl luminol_core::Window for Window { self.selected_armor_name = None; - let response = egui::Window::new(self.name()) + let name = if let Some(name) = &self.selected_armor_name { + format!("Editing armor {:?}", name) + } else { + "Armor Editor".into() + }; + + let response = egui::Window::new(name) .id(self.id()) .default_width(500.) .open(open) diff --git a/crates/ui/src/windows/classes.rs b/crates/ui/src/windows/classes.rs index 05360761..1124869b 100644 --- a/crates/ui/src/windows/classes.rs +++ b/crates/ui/src/windows/classes.rs @@ -99,14 +99,6 @@ impl Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - if let Some(name) = &self.selected_class_name { - format!("Editing class {:?}", name) - } else { - "Class Editor".into() - } - } - fn id(&self) -> egui::Id { egui::Id::new("class_editor") } @@ -133,7 +125,13 @@ impl luminol_core::Window for Window { self.selected_class_name = None; - let response = egui::Window::new(self.name()) + let name = if let Some(name) = &self.selected_class_name { + format!("Editing class {:?}", name) + } else { + "Class Editor".into() + }; + + let response = egui::Window::new(name) .id(self.id()) .default_width(500.) .open(open) diff --git a/crates/ui/src/windows/common_event_edit.rs b/crates/ui/src/windows/common_event_edit.rs index 538e1eea..1e828e6e 100644 --- a/crates/ui/src/windows/common_event_edit.rs +++ b/crates/ui/src/windows/common_event_edit.rs @@ -41,14 +41,6 @@ impl Default for Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - self.tabs - .focused_name() - .map_or("Common Events".to_string(), |name| { - format!("Editing Common Event {name}") - }) - } - fn id(&self) -> egui::Id { egui::Id::new("Common Events") } @@ -59,7 +51,13 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - egui::Window::new(self.name()) + let name = self + .tabs + .focused_name() + .map_or("Common Events".to_string(), |name| { + format!("Editing Common Event {name}") + }); + egui::Window::new(name) .default_width(500.) .id(egui::Id::new("common_events_edit")) .open(open) diff --git a/crates/ui/src/windows/config_window.rs b/crates/ui/src/windows/config_window.rs index 0839d76a..caac5401 100644 --- a/crates/ui/src/windows/config_window.rs +++ b/crates/ui/src/windows/config_window.rs @@ -30,10 +30,6 @@ pub struct Window {} impl Window {} impl luminol_core::Window for Window { - fn name(&self) -> String { - "Local Luminol Config".to_string() - } - fn id(&self) -> egui::Id { egui::Id::new("Local Luminol Config") } @@ -44,31 +40,33 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - egui::Window::new(self.name()).open(open).show(ctx, |ui| { - let config = update_state - .project_config - .as_mut() - .expect("project not open"); + egui::Window::new("Local Luminol Config") + .open(open) + .show(ctx, |ui| { + let config = update_state + .project_config + .as_mut() + .expect("project not open"); - ui.label("Project name"); - ui.text_edit_singleline(&mut config.project.project_name); - ui.label("Scripts path"); - ui.text_edit_singleline(&mut config.project.scripts_path); - ui.checkbox( - &mut config.project.use_ron, - "Use RON (Rusty Object Notation)", - ); - egui::ComboBox::from_label("RGSS Version") - .selected_text(config.project.rgss_ver.to_string()) - .show_ui(ui, |ui| { - for ver in luminol_config::RGSSVer::iter() { - ui.selectable_value(&mut config.project.rgss_ver, ver, ver.to_string()); - } - }); + ui.label("Project name"); + ui.text_edit_singleline(&mut config.project.project_name); + ui.label("Scripts path"); + ui.text_edit_singleline(&mut config.project.scripts_path); + ui.checkbox( + &mut config.project.use_ron, + "Use RON (Rusty Object Notation)", + ); + egui::ComboBox::from_label("RGSS Version") + .selected_text(config.project.rgss_ver.to_string()) + .show_ui(ui, |ui| { + for ver in luminol_config::RGSSVer::iter() { + ui.selectable_value(&mut config.project.rgss_ver, ver, ver.to_string()); + } + }); - ui.label("Playtest Executable"); - ui.text_edit_singleline(&mut config.project.playtest_exe); - }); + ui.label("Playtest Executable"); + ui.text_edit_singleline(&mut config.project.playtest_exe); + }); } fn requires_filesystem(&self) -> bool { diff --git a/crates/ui/src/windows/console.rs b/crates/ui/src/windows/console.rs index 0f34bd63..5e9ee7c4 100644 --- a/crates/ui/src/windows/console.rs +++ b/crates/ui/src/windows/console.rs @@ -39,10 +39,6 @@ impl Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - self.term.title.clone() - } - fn id(&self) -> egui::Id { self.term.id } @@ -57,7 +53,7 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - egui::Window::new(self.name()) + egui::Window::new(&self.term.title) .id(self.term.id) .open(open) .show(ctx, |ui| { diff --git a/crates/ui/src/windows/enemies.rs b/crates/ui/src/windows/enemies.rs index 070ea7a7..93c90952 100644 --- a/crates/ui/src/windows/enemies.rs +++ b/crates/ui/src/windows/enemies.rs @@ -247,14 +247,6 @@ impl Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - if let Some(name) = &self.selected_enemy_name { - format!("Editing enemy {:?}", name) - } else { - "Enemy Editor".into() - } - } - fn id(&self) -> egui::Id { egui::Id::new("enemy_editor") } @@ -283,7 +275,13 @@ impl luminol_core::Window for Window { self.selected_enemy_name = None; - let response = egui::Window::new(self.name()) + let name = if let Some(name) = &self.selected_enemy_name { + format!("Editing enemy {:?}", name) + } else { + "Enemy Editor".into() + }; + + let response = egui::Window::new(name) .id(self.id()) .default_width(500.) .open(open) diff --git a/crates/ui/src/windows/event_edit.rs b/crates/ui/src/windows/event_edit.rs index 9cfcb3db..519266e7 100644 --- a/crates/ui/src/windows/event_edit.rs +++ b/crates/ui/src/windows/event_edit.rs @@ -72,10 +72,6 @@ impl Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - format!("Event 'TODO' ID {}", self.event_id) - } - fn id(&self) -> egui::Id { egui::Id::new("luminol_event_edit") .with(self.map_id) @@ -100,7 +96,7 @@ impl luminol_core::Window for Window { let mut modified = false; let mut graphic_modified = false; - egui::Window::new(self.name()) + egui::Window::new(format!("Event '{}' ID {}", event.name, self.event_id)) .open(open) .id(self.id()) .show(ctx, |ui| { diff --git a/crates/ui/src/windows/global_config_window.rs b/crates/ui/src/windows/global_config_window.rs index 10912dff..db9c9f04 100644 --- a/crates/ui/src/windows/global_config_window.rs +++ b/crates/ui/src/windows/global_config_window.rs @@ -26,10 +26,6 @@ pub struct Window {} impl luminol_core::Window for Window { - fn name(&self) -> String { - "Luminol Preferences".to_string() - } - fn id(&self) -> egui::Id { egui::Id::new("luminol_preferences_window") } @@ -40,7 +36,7 @@ impl luminol_core::Window for Window { open: &mut bool, _update_state: &mut luminol_core::UpdateState<'_>, ) { - egui::Window::new(self.name()) + egui::Window::new("Luminol Preferences") .open(open) .show(ctx, |_ui| {}); } diff --git a/crates/ui/src/windows/items.rs b/crates/ui/src/windows/items.rs index c5fac9fe..d04b2f62 100644 --- a/crates/ui/src/windows/items.rs +++ b/crates/ui/src/windows/items.rs @@ -61,14 +61,6 @@ impl Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - if let Some(name) = &self.selected_item_name { - format!("Editing item {:?}", name) - } else { - "Item Editor".into() - } - } - fn id(&self) -> egui::Id { egui::Id::new("item_editor") } @@ -94,7 +86,13 @@ impl luminol_core::Window for Window { self.selected_item_name = None; - let response = egui::Window::new(self.name()) + let name = if let Some(name) = &self.selected_item_name { + format!("Editing item {:?}", name) + } else { + "Item Editor".into() + }; + + let response = egui::Window::new(name) .id(self.id()) .default_width(500.) .open(open) diff --git a/crates/ui/src/windows/misc.rs b/crates/ui/src/windows/misc.rs index 6f31057b..52a09c11 100644 --- a/crates/ui/src/windows/misc.rs +++ b/crates/ui/src/windows/misc.rs @@ -27,10 +27,6 @@ pub struct EguiInspection {} impl luminol_core::Window for EguiInspection { - fn name(&self) -> String { - "Egui Inspection".to_string() - } - fn id(&self) -> egui::Id { egui::Id::new("Egui Inspection") } @@ -41,7 +37,7 @@ impl luminol_core::Window for EguiInspection { open: &mut bool, _update_state: &mut luminol_core::UpdateState<'_>, ) { - egui::Window::new(self.name()) + egui::Window::new("Egui Inspection") .open(open) .show(ctx, |ui| ctx.inspection_ui(ui)); } @@ -52,10 +48,6 @@ impl luminol_core::Window for EguiInspection { pub struct EguiMemory {} impl luminol_core::Window for EguiMemory { - fn name(&self) -> String { - "Egui Memory".to_string() - } - fn id(&self) -> egui::Id { egui::Id::new("Egui Memory") } @@ -66,7 +58,7 @@ impl luminol_core::Window for EguiMemory { open: &mut bool, _update_state: &mut luminol_core::UpdateState<'_>, ) { - egui::Window::new(self.name()) + egui::Window::new("Egui Memory") .open(open) .show(ctx, |ui| ctx.memory_ui(ui)); } @@ -76,10 +68,6 @@ impl luminol_core::Window for EguiMemory { pub struct FilesystemDebug {} impl luminol_core::Window for FilesystemDebug { - fn name(&self) -> String { - "Filesystem Debug".to_string() - } - fn id(&self) -> egui::Id { egui::Id::new("Filesystem Debug Window") } @@ -90,7 +78,7 @@ impl luminol_core::Window for FilesystemDebug { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - egui::Window::new(self.name()) + egui::Window::new("Filesystem Debug") .open(open) .show(ctx, |ui| update_state.filesystem.debug_ui(ui)); } diff --git a/crates/ui/src/windows/new_project.rs b/crates/ui/src/windows/new_project.rs index 53516fdd..9bb59bb7 100644 --- a/crates/ui/src/windows/new_project.rs +++ b/crates/ui/src/windows/new_project.rs @@ -65,10 +65,6 @@ impl Default for Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - "New Project".to_string() - } - fn id(&self) -> egui::Id { egui::Id::new("New Project") } @@ -80,7 +76,7 @@ impl luminol_core::Window for Window { update_state: &mut luminol_core::UpdateState<'_>, ) { let mut win_open = true; - egui::Window::new(self.name()) + egui::Window::new("New Project") .open(&mut win_open) .show(ctx, |ui| { ui.add_enabled_ui( diff --git a/crates/ui/src/windows/reporter.rs b/crates/ui/src/windows/reporter.rs index bd6194d6..0ed38386 100644 --- a/crates/ui/src/windows/reporter.rs +++ b/crates/ui/src/windows/reporter.rs @@ -63,10 +63,6 @@ impl Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - "Crash Reporter".into() - } - fn id(&self) -> egui::Id { egui::Id::new("reporter") } @@ -94,7 +90,7 @@ impl luminol_core::Window for Window { let mut should_close = false; - egui::Window::new(self.name()) + egui::Window::new("Crash Reporter") .id(egui::Id::new("reporter")) .default_width(500.) .open(open) diff --git a/crates/ui/src/windows/script_edit.rs b/crates/ui/src/windows/script_edit.rs index 0d830e7c..0067ad77 100644 --- a/crates/ui/src/windows/script_edit.rs +++ b/crates/ui/src/windows/script_edit.rs @@ -36,14 +36,6 @@ impl Default for Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - self.tabs - .focused_name() - .map_or("Scripts".to_string(), |name| { - format!("Editing Script {name}") - }) - } - fn id(&self) -> egui::Id { egui::Id::new("Script Edit") } @@ -54,7 +46,13 @@ impl luminol_core::Window for Window { open: &mut bool, update_state: &mut luminol_core::UpdateState<'_>, ) { - egui::Window::new(self.name()) + let name = self + .tabs + .focused_name() + .map_or("Scripts".to_string(), |name| { + format!("Editing Script {name}") + }); + egui::Window::new(name) .open(open) .id(egui::Id::new("script_editor_window")) .show(ctx, |ui| { diff --git a/crates/ui/src/windows/skills.rs b/crates/ui/src/windows/skills.rs index d4c22727..5c410cc9 100644 --- a/crates/ui/src/windows/skills.rs +++ b/crates/ui/src/windows/skills.rs @@ -39,14 +39,6 @@ impl Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - if let Some(name) = &self.selected_skill_name { - format!("Editing skill {:?}", name) - } else { - "Skill Editor".into() - } - } - fn id(&self) -> egui::Id { egui::Id::new("skill_editor") } @@ -72,7 +64,13 @@ impl luminol_core::Window for Window { self.selected_skill_name = None; - let response = egui::Window::new(self.name()) + let name = if let Some(name) = &self.selected_skill_name { + format!("Editing skill {:?}", name) + } else { + "Skill Editor".into() + }; + + let response = egui::Window::new(name) .id(self.id()) .default_width(500.) .open(open) diff --git a/crates/ui/src/windows/states.rs b/crates/ui/src/windows/states.rs index 6794f0c8..6fb79435 100644 --- a/crates/ui/src/windows/states.rs +++ b/crates/ui/src/windows/states.rs @@ -39,14 +39,6 @@ impl Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - if let Some(name) = &self.selected_state_name { - format!("Editing state {:?}", name) - } else { - "State Editor".into() - } - } - fn id(&self) -> egui::Id { egui::Id::new("state_editor") } @@ -70,7 +62,13 @@ impl luminol_core::Window for Window { self.selected_state_name = None; - let response = egui::Window::new(self.name()) + let name = if let Some(name) = &self.selected_state_name { + format!("Editing state {:?}", name) + } else { + "State Editor".into() + }; + + let response = egui::Window::new(name) .id(self.id()) .default_width(500.) .open(open) diff --git a/crates/ui/src/windows/weapons.rs b/crates/ui/src/windows/weapons.rs index 81ba357a..1e439507 100644 --- a/crates/ui/src/windows/weapons.rs +++ b/crates/ui/src/windows/weapons.rs @@ -39,14 +39,6 @@ impl Window { } impl luminol_core::Window for Window { - fn name(&self) -> String { - if let Some(name) = &self.selected_weapon_name { - format!("Editing weapon {:?}", name) - } else { - "Weapon Editor".into() - } - } - fn id(&self) -> egui::Id { egui::Id::new("weapon_editor") } @@ -71,7 +63,13 @@ impl luminol_core::Window for Window { self.selected_weapon_name = None; - let response = egui::Window::new(self.name()) + let name = if let Some(name) = &self.selected_weapon_name { + format!("Editing weapon {:?}", name) + } else { + "Weapon Editor".into() + }; + + let response = egui::Window::new(name) .id(self.id()) .default_width(500.) .open(open) From b4ada77390076bea87b08f257101597685b6c418 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Thu, 4 Jul 2024 01:26:20 -0700 Subject: [PATCH 65/67] Fix AudioFile::default defaulting to 0 volume --- crates/data/src/shared/audio_file.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/data/src/shared/audio_file.rs b/crates/data/src/shared/audio_file.rs index 0675851c..79dd4824 100644 --- a/crates/data/src/shared/audio_file.rs +++ b/crates/data/src/shared/audio_file.rs @@ -16,7 +16,7 @@ // along with Luminol. If not, see . use crate::{optional_path_alox, optional_path_serde, Path}; -#[derive(Default, Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] #[derive(serde::Deserialize, serde::Serialize)] #[derive(alox_48::Deserialize, alox_48::Serialize)] #[marshal(class = "RPG::AudioFile")] @@ -27,3 +27,13 @@ pub struct AudioFile { pub volume: u8, pub pitch: u8, } + +impl Default for AudioFile { + fn default() -> Self { + Self { + name: None, + volume: 100, + pitch: 100, + } + } +} From 44df4fbf3752f885f735b7cdc00981364626f0d3 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Thu, 4 Jul 2024 01:28:07 -0700 Subject: [PATCH 66/67] Bump rodio version (fixes sound test crash?) --- Cargo.lock | 6 ++++-- crates/audio/Cargo.toml | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a1d693f..649ca5f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2982,6 +2982,7 @@ dependencies = [ "camino", "color-eyre", "flume", + "fragile", "luminol-filesystem", "once_cell", "oneshot", @@ -4530,15 +4531,16 @@ dependencies = [ [[package]] name = "rodio" -version = "0.17.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b1bb7b48ee48471f55da122c0044fcc7600cfcc85db88240b89cb832935e611" +checksum = "6006a627c1a38d37f3d3a85c6575418cfe34a5392d60a686d0071e1c8d427acb" dependencies = [ "claxon", "cpal", "hound", "lewton", "symphonia", + "thiserror", ] [[package]] diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index 1c00351e..0859aa4d 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -28,12 +28,13 @@ color-eyre.workspace = true thiserror.workspace = true luminol-filesystem.workspace = true +fragile.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -rodio = "0.17.3" +rodio = "0.19.0" [target.'cfg(target_arch = "wasm32")'.dependencies] -rodio = { version = "0.17.1", features = ["wasm-bindgen"] } +rodio = { version = "0.19.0", features = ["wasm-bindgen"] } web-sys = { version = "0.3", features = ["Window"] } flume.workspace = true From 69d3e34afb160ac521e5f171b71e5ea699d88109 Mon Sep 17 00:00:00 2001 From: Melody Madeline Lyons Date: Thu, 4 Jul 2024 01:28:33 -0700 Subject: [PATCH 67/67] Fix compiler warnings --- crates/ui/src/windows/common_event_edit.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/ui/src/windows/common_event_edit.rs b/crates/ui/src/windows/common_event_edit.rs index 1e828e6e..3ca369cc 100644 --- a/crates/ui/src/windows/common_event_edit.rs +++ b/crates/ui/src/windows/common_event_edit.rs @@ -28,14 +28,14 @@ use luminol_modals::database_modal; /// The common event editor. pub struct Window { tabs: luminol_core::Tabs, - selected_id: usize, + _selected_id: usize, } impl Default for Window { fn default() -> Self { Self { tabs: luminol_core::Tabs::new("common_event_tabs", false), - selected_id: 0, + _selected_id: 0, } } } @@ -49,7 +49,7 @@ impl luminol_core::Window for Window { &mut self, ctx: &egui::Context, open: &mut bool, - update_state: &mut luminol_core::UpdateState<'_>, + _update_state: &mut luminol_core::UpdateState<'_>, ) { let name = self .tabs @@ -61,7 +61,7 @@ impl luminol_core::Window for Window { .default_width(500.) .id(egui::Id::new("common_events_edit")) .open(open) - .show(ctx, |ui| { + .show(ctx, |_| { // TODO }); }