From b75ab073601a6d6a584ce8e1d4f29c4e101feab8 Mon Sep 17 00:00:00 2001 From: Vulae Date: Sun, 4 Aug 2024 18:36:36 -0500 Subject: [PATCH] use egui_dock for actual tab system --- Cargo.lock | 56 ++++++- Cargo.toml | 1 + src/app/app.rs | 176 ++++++++++---------- src/app/explorers/image.rs | 2 +- src/app/explorers/source_engine/vpk.rs | 2 +- src/app/explorers/source_engine/vtf.rs | 141 ++++++++-------- src/app/explorers/text.rs | 2 +- src/util/egui/mod.rs | 2 + src/util/egui/splitter.rs | 215 +++++++++++++++++++++++++ src/util/mod.rs | 1 + 10 files changed, 434 insertions(+), 164 deletions(-) create mode 100644 src/util/egui/mod.rs create mode 100644 src/util/egui/splitter.rs diff --git a/Cargo.lock b/Cargo.lock index 8f940e5..10b55d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -926,7 +926,7 @@ version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.72", @@ -1229,6 +1229,16 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "duplicate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", +] + [[package]] name = "ecolor" version = "0.28.1" @@ -1325,6 +1335,17 @@ dependencies = [ "winit", ] +[[package]] +name = "egui_dock" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629a8b0e440d69996795669ceacc0dd839a997843489273600d31d16c9cb3500" +dependencies = [ + "duplicate", + "egui", + "paste", +] + [[package]] name = "egui_glow" version = "0.28.1" @@ -1892,6 +1913,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -2846,6 +2873,30 @@ dependencies = [ "toml_edit 0.21.1", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -3444,7 +3495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", - "heck", + "heck 0.5.0", "pkg-config", "toml 0.8.16", "version-compare", @@ -3731,6 +3782,7 @@ dependencies = [ "dark-light", "eframe", "egui", + "egui_dock", "env_logger", "image", "rayon", diff --git a/Cargo.toml b/Cargo.toml index bdfe983..d4187d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ clap = { version = "4.5.11", features = ["derive"] } dark-light = "1.1.1" eframe = "0.28.1" egui = "0.28.1" +egui_dock = "0.13.0" env_logger = { version = "0.11.5", features = ["auto-color", "humantime"] } image = "0.25.2" rayon = "1.10.0" diff --git a/src/app/app.rs b/src/app/app.rs index 27a3fdd..01a2bd0 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -1,25 +1,61 @@ use std::{cell::RefCell, fs::File, io::{Read, Seek}, path::{Path, PathBuf}, rc::Rc}; use anyhow::{anyhow, Result}; -use uuid::Uuid; use super::explorers::{image::ImageExplorer, source_engine::{vpk::VpkExplorer, vtf::VtfExplorer}, text::TextExplorer}; +struct ExplorerTab; + +impl egui_dock::TabViewer for ExplorerTab { + type Tab = SharedExplorer; + + fn id(&mut self, tab: &mut Self::Tab) -> egui::Id { + tab.uuid().to_string().into() + } + + fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText { + tab.name().into() + } + + fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { + if let Err(err) = tab.ui(ui) { + println!("UI Error: {}", err); + } + } +} + + + pub struct AppContext { - pub explorers: Vec, - open_explorer: Uuid, + dock_state: egui_dock::DockState, + explorers_to_add: Vec, auto_focus_new_explorers: bool, } impl AppContext { - fn new() -> AppContext { - AppContext { - explorers: Vec::new(), - open_explorer: Uuid::nil(), + fn new() -> Self { + Self { + dock_state: egui_dock::DockState::new(Vec::new()), + explorers_to_add: Vec::new(), auto_focus_new_explorers: true, } } + + fn has_explorers(&self) -> bool { + self.dock_state.iter_all_tabs().count() > 0 + } + + fn new_explorer(&mut self, explorer: impl Explorer + 'static) { + self.explorers_to_add.push(SharedExplorer(Rc::new(RefCell::new(explorer)))); + } + + fn push_new_explorers_to_dock_state(&mut self) { + // TODO: Reimplement auto_focus_new_explorers + while let Some(explorer) = self.explorers_to_add.pop() { + self.dock_state.push_to_focused_leaf(explorer); + } + } } @@ -27,45 +63,16 @@ impl AppContext { #[derive(Clone)] -pub struct SharedAppContext { - app_context: Rc>, -} +pub struct SharedAppContext(Rc>); impl SharedAppContext { - pub fn new() -> SharedAppContext { + pub fn new() -> Self { let app_context = AppContext::new(); - SharedAppContext { - app_context: Rc::new(RefCell::new(app_context)), - } - } - - pub fn explorers(&self) -> Vec { - // This sucks, please do not collect! - self.app_context.borrow_mut().explorers.iter().map(|v| v.clone()).collect::>() - } - - pub fn current_explorer(&self) -> Option { - let open_explorer_uuid = self.app_context.borrow().open_explorer; - self.explorers() - .iter() - .find(|explorer| explorer.uuid() == open_explorer_uuid) - .map(|explorer| explorer.clone()) - } - - pub fn select_explorer(&self, uuid: Uuid) { - self.app_context.borrow_mut().open_explorer = uuid; + Self(Rc::new(RefCell::new(app_context))) } pub fn new_explorer(&mut self, explorer: impl Explorer + 'static) { - if self.app_context.borrow().auto_focus_new_explorers { - self.app_context.borrow_mut().open_explorer = explorer.uuid(); - } - self.app_context.borrow_mut().explorers.push(SharedExplorer(Rc::new(RefCell::new(explorer)))); - } - - pub fn remove_explorer(&mut self, uuid: Uuid) { - // TODO: Clean up. What in the heck is this? - self.app_context.borrow_mut().explorers = self.explorers().into_iter().filter(|e| e.uuid() != uuid).collect::>(); + self.0.borrow_mut().new_explorer(explorer); } @@ -121,7 +128,7 @@ impl eframe::App for SharedAppContext { dark_light::Mode::Default => catppuccin_egui::MOCHA, }); - self.app_context.borrow_mut().auto_focus_new_explorers = !ctx.input(|i| i.modifiers.shift); + self.0.borrow_mut().auto_focus_new_explorers = !ctx.input(|i| i.modifiers.shift); let files = ctx.input(|i| i.raw.dropped_files.clone()); if !files.is_empty() { @@ -131,56 +138,49 @@ impl eframe::App for SharedAppContext { } } } + + self.0.borrow_mut().push_new_explorers_to_dock_state(); - egui::CentralPanel::default().frame(egui::Frame::none()).show(ctx, |ui| { - - egui::SidePanel::left("tab_list").show(ui.ctx(), |ui| { - ui.vertical(|ui| { - ui.label("Tab List"); - egui::ScrollArea::vertical().show(ui, |ui| { - ui.vertical(|ui| { - let selected_explorer: Option = self.current_explorer().map(|e| e.uuid()); - for explorer in self.explorers().iter_mut() { - let is_selected_explorer = selected_explorer.map(|u| u == explorer.uuid()).unwrap_or(false); - - ui.horizontal(|ui| { - ui.style_mut().spacing.item_spacing = egui::Vec2::new(0.0, 0.0); - - if ui.add( - egui::Button::new(explorer.name()) - .selected(is_selected_explorer) - ).clicked() { - self.select_explorer(explorer.uuid()); - } - - if ui.add( - egui::Button::new("X") - .selected(is_selected_explorer) - ).clicked() { - self.remove_explorer(explorer.uuid()) - } - }); - } - }); - }); - }); - }); + if self.0.borrow_mut().has_explorers() { + // TODO: Probably want to refactor this thing. + // Having to clone the dock state, then set it to avoid BorrowMutError is just bad. + let mut dock_state = self.0.borrow_mut().dock_state.clone(); + egui::CentralPanel::default() - .frame(egui::Frame::central_panel(ui.style()).multiply_with_opacity(0.5)) - .show(ui.ctx(), |ui| { - egui::ScrollArea::both() - .auto_shrink([ false, false ]) - .show(ui, |ui| { - if let Some(mut explorer) = self.current_explorer() { - if let Err(err) = explorer.update(ui) { - println!("{}", err); - } - } - }); + .frame( + egui::Frame::central_panel(&ctx.style()) + .multiply_with_opacity(0.5) + ) + .show(ctx, |ui| { + egui_dock::DockArea::new(&mut dock_state) + .style(egui_dock::Style::from_egui(ui.style().as_ref())) + .show_inside(ui, &mut ExplorerTab); }); + + self.0.borrow_mut().dock_state = dock_state; - }); + } else { + + egui::CentralPanel::default() + .frame( + egui::Frame::central_panel(&ctx.style()) + .multiply_with_opacity(0.5) + .inner_margin(96.0) + ) + .show(ctx, |ui| { + ui.centered_and_justified(|ui| { + // FIXME: Text spacing when wrapping. + ui.add(egui::Label::new( + egui::RichText::new("Drag & drop files to view") + .text_style(egui::TextStyle::Heading) + .size(64.0) + .strong() + )); + }); + }); + + } } } @@ -189,7 +189,7 @@ impl eframe::App for SharedAppContext { pub trait Explorer { fn uuid(&self) -> uuid::Uuid; fn name(&mut self) -> String { "Unnamed Tab".to_owned() } - fn update(&mut self, ui: &mut egui::Ui) -> Result<()>; + fn ui(&mut self, ui: &mut egui::Ui) -> Result<()>; } @@ -200,7 +200,7 @@ pub struct SharedExplorer(Rc>); impl Explorer for SharedExplorer { fn uuid(&self) -> uuid::Uuid { self.0.borrow().uuid() } fn name(&mut self) -> String { self.0.borrow_mut().name() } - fn update(&mut self, ui: &mut egui::Ui) -> Result<()> { self.0.borrow_mut().update(ui) } + fn ui(&mut self, ui: &mut egui::Ui) -> Result<()> { self.0.borrow_mut().ui(ui) } } diff --git a/src/app/explorers/image.rs b/src/app/explorers/image.rs index f6092be..e5a7241 100644 --- a/src/app/explorers/image.rs +++ b/src/app/explorers/image.rs @@ -59,7 +59,7 @@ impl Explorer for ImageExplorer { self.name.clone().unwrap_or("Image".to_owned()) } - fn update(&mut self, ui: &mut egui::Ui) -> Result<()> { + fn ui(&mut self, ui: &mut egui::Ui) -> Result<()> { let texture = self.texture.get_or_insert_with(|| { crate::util::image::image_egui_handle(&self.image, ui.ctx()) }); diff --git a/src/app/explorers/source_engine/vpk.rs b/src/app/explorers/source_engine/vpk.rs index da1ea6f..255a166 100644 --- a/src/app/explorers/source_engine/vpk.rs +++ b/src/app/explorers/source_engine/vpk.rs @@ -103,7 +103,7 @@ impl Explorer for VpkExplorer { self.name.clone().unwrap_or("VPK Archive".to_owned()) } - fn update(&mut self, ui: &mut egui::Ui) -> Result<()> { + fn ui(&mut self, ui: &mut egui::Ui) -> Result<()> { VpkExplorer::update_node(&mut self.app_context.clone(), ui, &mut self.node); Ok(()) } diff --git a/src/app/explorers/source_engine/vtf.rs b/src/app/explorers/source_engine/vtf.rs index f6c806e..fe523a3 100644 --- a/src/app/explorers/source_engine/vtf.rs +++ b/src/app/explorers/source_engine/vtf.rs @@ -69,90 +69,89 @@ impl Explorer for VtfExplorer { self.name.clone().unwrap_or("VTF Texture".to_owned()) } - fn update(&mut self, ui: &mut egui::Ui) -> Result<()> { + fn ui(&mut self, ui: &mut egui::Ui) -> Result<()> { - egui::SidePanel::right("vtf_information").show(ui.ctx(), |ui| { + crate::util::egui::splitter::Splitter::horizontal(self.uuid).min_size(240.0).show(ui, |ui_a, ui_b| { - ui.label("VTF Information"); - ui.label(format!("Format: {:?}", self.vtf.format())); - ui.label(format!("Size: {}x{}", self.vtf.width(), self.vtf.height())); - - if self.vtf.total_num_textures() > 1 { - ui.add_space(32.0); - } - - ui.horizontal(|ui| { - if self.vtf.mipmaps() > 1 { - ui.menu_button(format!("Mipmap {}", self.mipmap), |ui| { - for mipmap in 0..self.vtf.mipmaps() { - if ui.button(format!("Mipmap {}", mipmap)).clicked() { - self.mipmap = mipmap; - } - } - }); + ui_a.vertical(|ui| { + ui.label("VTF Information"); + ui.label(format!("Format: {:?}", self.vtf.format())); + ui.label(format!("Size: {}x{}", self.vtf.width(), self.vtf.height())); + + if self.vtf.total_num_textures() > 1 { + ui.add_space(32.0); } - if self.vtf.faces() > 1 { - ui.menu_button(format!("Face {}", self.face), |ui| { - for face in 0..self.vtf.faces() { - if ui.button(format!("Face {}", face)).clicked() { - self.face = face; + + ui.horizontal(|ui| { + if self.vtf.mipmaps() > 1 { + ui.menu_button(format!("Mipmap {}", self.mipmap), |ui| { + for mipmap in 0..self.vtf.mipmaps() { + if ui.button(format!("Mipmap {}", mipmap)).clicked() { + self.mipmap = mipmap; + } } - } - }); - } - if self.vtf.slices() > 1 { - ui.menu_button(format!("Slice {}", self.slice), |ui| { - for slice in 0..self.vtf.slices() { - if ui.button(format!("Slice {}", slice)).clicked() { - self.slice = slice; + }); + } + if self.vtf.faces() > 1 { + ui.menu_button(format!("Face {}", self.face), |ui| { + for face in 0..self.vtf.faces() { + if ui.button(format!("Face {}", face)).clicked() { + self.face = face; + } } - } + }); + } + if self.vtf.slices() > 1 { + ui.menu_button(format!("Slice {}", self.slice), |ui| { + for slice in 0..self.vtf.slices() { + if ui.button(format!("Slice {}", slice)).clicked() { + self.slice = slice; + } + } + }); + } + }); + + if self.vtf.frames() > 1 { + ui.add(egui::Slider::new(&mut self.frame, 0..=(self.vtf.frames() - 1)).text("Frame")); + } + + if let Some(thumbnail) = self.vtf.thumbnail() { + let thumbnail_source = self.thumbnail.get_or_insert_with(|| { + crate::util::image::image_egui_handle(&thumbnail.to_image(), ui.ctx()) }); + + ui.add_space(32.0); + ui.horizontal(|ui| { + ui.image(egui::ImageSource::Texture(egui::load::SizedTexture::from_handle(thumbnail_source))); + ui.label("Thumbnail"); + }); + ui.label(format!("Format: {:?}", thumbnail.format())); + ui.label(format!("Size: {}x{}", thumbnail.width(), thumbnail.height())); } }); - if self.vtf.frames() > 1 { - ui.add(egui::Slider::new(&mut self.frame, 0..=(self.vtf.frames() - 1)).text("Frame")); + if let Some(texture_handle_index) = self.vtf.texture_index(self.mipmap, self.frame, self.face, self.slice) { + if let Some(texture) = self.vtf.texture(self.mipmap, self.frame, self.face, self.slice) { + let texture_handle = self.textures[texture_handle_index].get_or_insert_with(|| { + crate::util::image::image_egui_handle(&texture.to_image(), ui_b.ctx()) + }); + ui_b.add_sized( + ui_b.available_size(), + egui::Image::new(egui::ImageSource::Texture(egui::load::SizedTexture::from_handle(&texture_handle))).shrink_to_fit(), + ).context_menu(|ui| { + if ui.button("Save Texture").clicked() { + crate::util::image::save_image( + &texture.to_image(), + self.name.clone().map(|filename| filename.trim_end_matches(".vtf").to_owned()), + ).expect("Failed to save VTF image"); + } + }); + } } - if let Some(thumbnail) = self.vtf.thumbnail() { - let thumbnail_source = self.thumbnail.get_or_insert_with(|| { - crate::util::image::image_egui_handle(&thumbnail.to_image(), ui.ctx()) - }); - - ui.add_space(32.0); - ui.horizontal(|ui| { - ui.image(egui::ImageSource::Texture(egui::load::SizedTexture::from_handle(thumbnail_source))); - ui.label("Thumbnail"); - }); - ui.label(format!("Format: {:?}", thumbnail.format())); - ui.label(format!("Size: {}x{}", thumbnail.width(), thumbnail.height())); - } }); - egui::CentralPanel::default() - .frame(egui::Frame::central_panel(ui.style()).multiply_with_opacity(0.0)) - .show(ui.ctx(), |ui| { - if let Some(texture_handle_index) = self.vtf.texture_index(self.mipmap, self.frame, self.face, self.slice) { - if let Some(texture) = self.vtf.texture(self.mipmap, self.frame, self.face, self.slice) { - let texture_handle = self.textures[texture_handle_index].get_or_insert_with(|| { - crate::util::image::image_egui_handle(&texture.to_image(), ui.ctx()) - }); - ui.add_sized( - ui.available_size(), - egui::Image::new(egui::ImageSource::Texture(egui::load::SizedTexture::from_handle(&texture_handle))).shrink_to_fit(), - ).context_menu(|ui| { - if ui.button("Save Texture").clicked() { - crate::util::image::save_image( - &texture.to_image(), - self.name.clone().map(|filename| filename.trim_end_matches(".vtf").to_owned()), - ).expect("Failed to save VTF image"); - } - }); - } - } - }); - Ok(()) } } diff --git a/src/app/explorers/text.rs b/src/app/explorers/text.rs index ccc2323..b8f72fb 100644 --- a/src/app/explorers/text.rs +++ b/src/app/explorers/text.rs @@ -64,7 +64,7 @@ impl Explorer for TextExplorer { self.name.clone().unwrap_or("Text".to_owned()) } - fn update(&mut self, ui: &mut egui::Ui) -> Result<()> { + fn ui(&mut self, ui: &mut egui::Ui) -> Result<()> { // TODO: Don't use egui::TextEdit, this should not be editable. ui.add( egui::TextEdit::multiline(&mut self.text) diff --git a/src/util/egui/mod.rs b/src/util/egui/mod.rs new file mode 100644 index 0000000..5619de7 --- /dev/null +++ b/src/util/egui/mod.rs @@ -0,0 +1,2 @@ + +pub mod splitter; diff --git a/src/util/egui/splitter.rs b/src/util/egui/splitter.rs new file mode 100644 index 0000000..cfd62e8 --- /dev/null +++ b/src/util/egui/splitter.rs @@ -0,0 +1,215 @@ + +// https://github.com/emilk/egui/issues/944#issuecomment-1809865470 + +use std::hash::Hash; + +use egui::{CursorIcon, Id, Layout, Pos2, Rect, Rounding, Sense, Ui, Vec2}; + +/// An axis that a Splitter can use +#[derive(Copy, Clone, Debug)] +pub enum SplitterAxis { + Horizontal, + Vertical, +} + +impl SplitterAxis { + pub fn best(rect: Rect) -> SplitterAxis { + if rect.width() >= rect.height() { + SplitterAxis::Horizontal + } else { + SplitterAxis::Vertical + } + } +} + +/// The internal data used by a splitter. Stored into memory +#[derive(Debug, Clone)] +struct SplitterData { + axis: SplitterAxis, + pos: f32, + min_size: f32, +} + +/// Splits a ui in half, using a draggable separator in the middle. +/// +pub struct Splitter { + id: Id, + data: SplitterData, +} +impl Splitter { + + /// Create a new Splitter + pub fn new(id_source: impl Hash, axis: SplitterAxis) -> Self { + Self { + id: Id::new(id_source), + data: SplitterData { + axis, + pos: 0.5, + min_size: 0.0, + }, + } + } + + /// Create a new vertical Splitter + pub fn vertical(id_source: impl Hash) -> Self { + Self::new(id_source, SplitterAxis::Vertical) + } + + /// Create a new horizontal Splitter + pub fn horizontal(id_source: impl Hash) -> Self { + Self::new(id_source, SplitterAxis::Horizontal) + } + + /// Sets the minimum allowed size for the area + pub fn min_size(mut self, points: f32) -> Self { + self.data.min_size = points; + self + } + + /// Thes the default position of the splitter separator. Usually it sits in the center, this moves it around. + pub fn default_pos(mut self, pos: f32) -> Self { + self.data.pos = pos; + self + } + + /// Show the splitter and fill it with content. + /// + /// ``` + /// Splitter::new("some_plot_split", SplitterAxis::Vertical) + /// .min_size(250.0) + /// .default_pos(2.0 / 3.0) + /// .show(ui, |ui_a, ui_b| { + /// Plot::new("plot_a") + /// .legend(Legend::default()) + /// .x_axis_formatter(log_formatter) + /// .y_axis_formatter(log_formatter) + /// .x_axis_label("X Axis") + /// .y_axis_label("A Axis") + /// .link_axis("axis_link", true, false) + /// .link_cursor("cursor_link", true, false) + /// .show(ui_a, |plot_ui| { + /// for line in plot_a_lines { + /// plot_ui.line(line); + /// } + /// }); + /// + /// Plot::new("plot_b") + /// .legend(Legend::default()) + /// .x_axis_formatter(log_formatter) + /// .x_axis_label("X Axis") + /// .y_axis_label("Y Axis") + /// .link_axis("axis_link", true, false) + /// .link_cursor("cursor_link", true, false) + /// .show(ui_b, |plot_ui| { + /// for line in plot_b_lines { + /// plot_ui.line(line); + /// } + /// }); + /// }); + /// ``` + pub fn show(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui, &mut Ui)) { + let mut data = if let Some(d) = ui.memory(|mem| mem.data.get_temp(self.id)) { + d + } else { + self.data.clone() + }; + + let sep_size = 10.0; + let sep_stroke = 2.0; + let whole_area = ui.available_size(); + + let split_axis_size = match data.axis { + SplitterAxis::Horizontal => whole_area.x, + SplitterAxis::Vertical => whole_area.y, + }; + let split_a_size = (split_axis_size - sep_size) * data.pos; + let split_b_size = split_axis_size - sep_size - split_a_size; + + let child_size_a = match data.axis { + SplitterAxis::Horizontal => Vec2::new(split_a_size, whole_area.y), + SplitterAxis::Vertical => Vec2::new(whole_area.x, split_a_size), + }; + + let child_size_b = match data.axis { + SplitterAxis::Horizontal => Vec2::new(split_b_size, whole_area.y), + SplitterAxis::Vertical => Vec2::new(whole_area.x, split_b_size), + }; + + let child_rect_a = Rect::from_min_size(ui.next_widget_position(), child_size_a); + let mut ui_a = ui.child_ui(child_rect_a, Layout::default(), None); + + let sep_rect = match data.axis { + SplitterAxis::Horizontal => Rect::from_min_size( + Pos2::new(child_rect_a.max.x, child_rect_a.min.y), + Vec2::new(sep_size, whole_area.y), + ), + SplitterAxis::Vertical => Rect::from_min_size( + Pos2::new(child_rect_a.min.x, child_rect_a.max.y), + Vec2::new(whole_area.x, sep_size), + ), + }; + + let resp = ui.allocate_rect(sep_rect, Sense::hover().union(Sense::click_and_drag())); + + let sep_draw_rect = match data.axis { + SplitterAxis::Horizontal => Rect::from_min_size( + Pos2::new( + sep_rect.min.x + sep_size / 2.0 - sep_stroke / 2.0, + sep_rect.min.y, + ), + Vec2::new(sep_stroke, sep_rect.height()), + ), + SplitterAxis::Vertical => Rect::from_min_size( + Pos2::new( + sep_rect.min.x, + sep_rect.min.y + sep_size / 2.0 - sep_stroke / 2.0, + ), + Vec2::new(sep_rect.width(), sep_stroke), + ), + }; + ui.painter().rect_filled( + sep_draw_rect, + Rounding::ZERO, + ui.style().visuals.noninteractive().bg_stroke.color, + ); + + let child_rect_b = match data.axis { + SplitterAxis::Horizontal => { + Rect::from_min_size(Pos2::new(sep_rect.max.x, sep_rect.min.y), child_size_b) + } + SplitterAxis::Vertical => { + Rect::from_min_size(Pos2::new(sep_rect.min.x, sep_rect.max.y), child_size_b) + } + }; + let mut ui_b = ui.child_ui(child_rect_b, Layout::default(), None); + + add_contents(&mut ui_a, &mut ui_b); + + if resp.hovered() { + match data.axis { + SplitterAxis::Horizontal => ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal), + SplitterAxis::Vertical => ui.ctx().set_cursor_icon(CursorIcon::ResizeVertical), + } + } + + if resp.dragged() { + let delta_pos = match data.axis { + SplitterAxis::Horizontal => resp.drag_delta().x / whole_area.x, + SplitterAxis::Vertical => resp.drag_delta().y / whole_area.y, + }; + + data.pos += delta_pos; + } + + // clip pos + let min_pos = (data.min_size / split_axis_size).min(1.0); + let max_pos = (1.0 - min_pos).max(0.0); + data.pos = data.pos.max(min_pos).min(max_pos); + + ui.memory_mut(|mem| { + mem.data.insert_temp(self.id, data); + }) + } +} + + diff --git a/src/util/mod.rs b/src/util/mod.rs index 000cd8a..4828939 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -3,6 +3,7 @@ pub mod image; pub mod source_engine; pub mod reader; pub mod texture; +pub mod egui; // pub mod virtual_fs; use std::{io::{self, Read, Seek}, path::PathBuf, sync::{Arc, Mutex}};