From e21ed4ff1fc4e96036ad02044329084d205961a3 Mon Sep 17 00:00:00 2001 From: Konkitoman Date: Mon, 9 Oct 2023 21:21:46 +0300 Subject: [PATCH] refactored the viewports example I added drag and drop on every viewport, to make possible in the future to drag and drop between viewports --- examples/viewports/src/main.rs | 588 +++++++++++++++++++++------------ 1 file changed, 382 insertions(+), 206 deletions(-) diff --git a/examples/viewports/src/main.rs b/examples/viewports/src/main.rs index 278c40f32..759ba7cd8 100644 --- a/examples/viewports/src/main.rs +++ b/examples/viewports/src/main.rs @@ -1,8 +1,8 @@ use egui::mutex::RwLock; use std::sync::Arc; -use eframe::egui; -use eframe::egui::ViewportBuilder; +use eframe::egui::{self, InnerResponse}; +use eframe::egui::{Id, ViewportBuilder}; use eframe::NativeOptions; #[derive(Default)] @@ -13,15 +13,9 @@ pub struct App { async_viewport_state: Arc>, sync_viewport_state: usize, - async_show_async_viewport: Arc>, - async_show_sync_viewport: Arc>, - async_async_viewport_state: Arc>, async_sync_viewport_state: Arc>, - sync_show_async_viewport: bool, - sync_show_sync_viewport: bool, - sync_async_viewport_state: Arc>, sync_sync_viewport_state: usize, } @@ -29,7 +23,7 @@ pub struct App { impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { - ui_info(ui); + generic_ui(ui, "Central Pannel"); ui.label("Look at the \"Frame: \" will tell you, what viewport is rendering!"); { let mut force_embedding = ctx.force_embedding(); @@ -40,215 +34,281 @@ impl eframe::App for App { ui.checkbox(&mut self.show_sync_viewport, "Show Sync Viewport"); let ctx = ui.ctx(); + // Showing Async Viewport if self.show_async_viewport { - let state = self.async_viewport_state.clone(); + let async_state = self.async_async_viewport_state.clone(); + let sync_state = self.async_sync_viewport_state.clone(); - let show_async_viewport2 = self.async_show_async_viewport.clone(); - let show_sync_viewport2 = self.async_show_sync_viewport.clone(); - - let async_viewport_state2 = self.async_async_viewport_state.clone(); - let sync_viewport_state2 = self.async_sync_viewport_state.clone(); - - ctx.create_viewport( - ViewportBuilder::new("Async Viewport").with_title("Async Viewport"), - move |ctx| { - let mut state = state.write(); - - let mut show_async_viewport2 = show_async_viewport2.write(); - let mut show_sync_viewport2 = show_sync_viewport2.write(); - - let async_viewport_state2 = async_viewport_state2.clone(); - let sync_viewport_state2 = sync_viewport_state2.clone(); - - let content = move |ui: &mut egui::Ui| { - ui_info(ui); - - ui.checkbox(&mut show_async_viewport2, "Show Async Viewport 2"); - ui.checkbox(&mut show_sync_viewport2, "Show Sync Viewport 2"); - - ui.label(format!("Count: {}", *state)); - if ui.button("Add").clicked() { - *state += 1; - } - - if *show_async_viewport2 { - ctx.create_viewport( - ViewportBuilder::new("Async Viewport in Async Viewport") - .with_title("Async Viewport in Async Viewport"), - move |ctx| { - let mut state = async_viewport_state2.write(); - - let content = move |ui: &mut egui::Ui| { - ui_info(ui); - - ui.label(format!("Count: {}", *state)); - if ui.button("Add").clicked() { - *state += 1; - } - - if ui.button("Set parent pos {0, 0}").clicked() { - let ctx = ui.ctx().clone(); - let parent_id = ctx.parent_viewport_id(); - ctx.viewport_command_for( - parent_id, - egui::ViewportCommand::OuterPosition( - egui::pos2(0.0, 0.0), - ), - ); - } - }; - - show_as_popup( - ctx, - "Async Viewport in Async Viewport", - content, - ); - }, - ); - } - - if *show_sync_viewport2 { - ctx.create_viewport_sync( - ViewportBuilder::new("Sync Viewport in Async Viewport") - .with_title("Sync Viewport in Async Viewport"), - move |ctx| { - let mut state = sync_viewport_state2.write(); - - let content = move |ui: &mut egui::Ui| { - ui_info(ui); - - ui.label(format!("Count: {}", *state)); - if ui.button("Add").clicked() { - *state += 1; - } - - if ui.button("Set parent pos {0, 0}").clicked() { - let ctx = ui.ctx().clone(); - let parent_id = ctx.parent_viewport_id(); - ctx.viewport_command_for( - parent_id, - egui::ViewportCommand::OuterPosition( - egui::pos2(0.0, 0.0), - ), - ); - } - }; - - show_as_popup( - ctx, - "Sync Viewport in Async Viewport", - content, - ); - }, - ); - } - }; - - show_as_popup(ctx, "Async Viewport", content); - }, + show_async_viewport( + ctx, + "Async Viewport", + self.async_viewport_state.clone(), + vec![ + AsyncViewport::new("Async Viewport in Async Viewport", move |ctx| { + show_async_viewport( + ctx, + "AA Async Viewport in Async Viewport", + async_state.clone(), + vec![], + ); + }), + AsyncViewport::new("Sync Viewport in Async Viewport", move |ctx| { + let mut state = sync_state.write(); + show_sync_viewport( + ctx, + "AS Sync Viewport in Async Viewport", + &mut state, + vec![], + ); + }), + ], ); } // Showing Sync Viewport if self.show_sync_viewport { - ctx.create_viewport_sync( - ViewportBuilder::new("Sync Viewport").with_title("Sync Viewport"), - |ctx| { - let async_viewport_state3 = self.sync_async_viewport_state.clone(); - - let content = |ui: &mut egui::Ui| { - ui_info(ui); - - ui.checkbox(&mut self.sync_show_async_viewport, "Show Async Viewport"); - ui.checkbox(&mut self.sync_show_sync_viewport, "Show Sync Viewport"); - - ui.label(format!("Count: {}", self.sync_viewport_state)); - if ui.button("Add").clicked() { - self.sync_viewport_state += 1; - } - - if self.sync_show_async_viewport { - ctx.create_viewport( - ViewportBuilder::new("Async Viewport in Sync Viewport") - .with_title("Async Viewport in Sync Viewport"), - move |ctx| { - let mut state = async_viewport_state3.write(); - - let content = move |ui: &mut egui::Ui| { - ui_info(ui); - - ui.label(format!("Count: {}", *state)); - if ui.button("Add").clicked() { - *state += 1; - } - - if ui.button("Set parent pos {0, 0}").clicked() { - let ctx = ui.ctx().clone(); - let parent_id = ctx.parent_viewport_id(); - ctx.viewport_command_for( - parent_id, - egui::ViewportCommand::OuterPosition( - egui::pos2(0.0, 0.0), - ), - ); - } - }; - - show_as_popup( - ctx, - "Async Viewport in Sync Viewport", - content, - ); - }, - ); - } - - if self.sync_show_sync_viewport { - ctx.create_viewport_sync( - ViewportBuilder::new("Sync Viewport in Sync Viewport") - .with_title("Sync Viewport in Sync Viewport"), - move |ctx| { - let state = &mut self.sync_sync_viewport_state; - - let content = move |ui: &mut egui::Ui| { - ui_info(ui); - - ui.label(format!("Count: {}", *state)); - if ui.button("Add").clicked() { - *state += 1; - } - - if ui.button("Set parent pos {0, 0}").clicked() { - let ctx = ui.ctx().clone(); - let parent_id = ctx.parent_viewport_id(); - ctx.viewport_command_for( - parent_id, - egui::ViewportCommand::OuterPosition( - egui::pos2(0.0, 0.0), - ), - ); - } - }; - - show_as_popup( - ctx, - "Sync Viewport in Sync Viewport", - content, - ); - }, - ); - } - }; - - show_as_popup(ctx, "Sync Viewport", content); - }, + let async_state = self.sync_async_viewport_state.clone(); + let sync_state = &mut self.sync_sync_viewport_state; + show_sync_viewport( + ctx, + "Sync Viewport", + &mut self.sync_viewport_state, + vec![ + SyncViewport::new("Async Viewport in Sync Viewport", move |ctx| { + show_async_viewport( + ctx, + "SA Async Viewport in Sync Viewport", + async_state.clone(), + vec![], + ); + }), + SyncViewport::new("Sync Viewport in Sync Viewport", move |ctx| { + show_sync_viewport( + ctx, + "SS Sync Viewport in Sync Viewport", + sync_state, + vec![], + ); + }), + ], ); } }); } } +#[derive(Default, Clone)] +struct State { + active: Vec, +} + +#[derive(Clone)] +struct AsyncViewport { + name: String, + init: Arc>, +} + +impl AsyncViewport { + fn new(name: impl Into, init: impl Fn(&egui::Context) + Sync + Send + 'static) -> Self { + Self { + name: name.into(), + init: Arc::new(Box::new(init)), + } + } +} + +fn show_async_viewport( + ctx: &egui::Context, + name: impl Into, + count: Arc>, + viewports: Vec, +) { + let name: String = name.into(); + + ctx.create_viewport( + ViewportBuilder::new(name.clone()) + .with_title(name.as_str()) + .with_inner_size(Some(egui::vec2(450.0, 320.0))), + move |ctx| { + let mut count = count.write(); + let viewports = viewports.clone(); + + let n = name.clone(); + let name = n.clone(); + + let content = move |ui: &mut egui::Ui| { + generic_ui(ui, name.clone()); + + let ctx = ui.ctx().clone(); + let mut state = ctx.memory_mut(|mem| { + mem.data + .get_temp_mut_or_default::(name.clone().into()) + .clone() + }); + + state.active.resize(viewports.len(), false); + + for (i, viewport) in viewports.iter().enumerate() { + ui.checkbox(&mut state.active[i], &viewport.name); + } + + ui.add(egui::DragValue::new(&mut *count).prefix("Count: ")); + if ui.button("Add").clicked() { + *count += 1; + } + + for (i, viewport) in viewports.iter().enumerate() { + if state.active[i] { + (viewport.init)(&ctx); + } + } + + ctx.memory_mut(move |mem| { + *mem.data.get_temp_mut_or_default::(name.into()) = state; + }); + }; + + show_as_popup(ctx, &n, content); + }, + ); +} + +struct SyncViewport<'a> { + name: String, + init: Box, +} + +impl<'a> SyncViewport<'a> { + fn new(name: impl Into, init: impl FnMut(&egui::Context) + 'a) -> Self { + Self { + name: name.into(), + init: Box::new(init), + } + } +} + +fn show_sync_viewport( + ctx: &egui::Context, + name: impl Into, + count: &mut usize, + mut viewports: Vec>, +) { + let name: String = name.into(); + + ctx.create_viewport_sync( + ViewportBuilder::new(name.clone()) + .with_title(name.as_str()) + .with_inner_size(Some(egui::vec2(450.0, 320.0))), + move |ctx| { + let n = name.clone(); + + let content = |ui: &mut egui::Ui| { + generic_ui(ui, name.clone()); + + let ctx = ui.ctx().clone(); + let mut state = ctx.memory_mut(|mem| { + mem.data + .get_temp_mut_or_default::(name.clone().into()) + .clone() + }); + + state.active.resize(viewports.len(), false); + + for (i, viewport) in viewports.iter().enumerate() { + ui.checkbox(&mut state.active[i], &viewport.name); + } + + ui.add(egui::DragValue::new(&mut *count).prefix("Count: ")); + if ui.button("Add").clicked() { + *count += 1; + } + for (i, viewport) in viewports.iter_mut().enumerate() { + if state.active[i] { + (viewport.init)(&ctx); + } + } + + ctx.memory_mut(move |mem| { + *mem.data.get_temp_mut_or_default::(name.into()) = state; + }); + }; + + show_as_popup(ctx, &n, content); + }, + ); +} + +// This is taken from crates/egui_demo_lib/src/debo/drag_and_drop.rs +fn drag_source( + ui: &mut egui::Ui, + id: egui::Id, + body: impl FnOnce(&mut egui::Ui) -> R, +) -> InnerResponse { + let is_being_dragged = ui.memory(|mem| mem.is_being_dragged(id)); + + if !is_being_dragged { + let res = ui.scope(body); + + // Check for drags: + let response = ui.interact(res.response.rect, id, egui::Sense::drag()); + if response.hovered() { + ui.ctx().set_cursor_icon(egui::CursorIcon::Grab); + } + res + } else { + ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing); + + // Paint the body to a new layer: + let layer_id = egui::LayerId::new(egui::Order::Tooltip, id); + let res = ui.with_layer_id(layer_id, body); + + if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() { + let delta = pointer_pos - res.response.rect.center(); + ui.ctx().translate_layer(layer_id, delta); + } + + res + } +} + +// This is taken from crates/egui_demo_lib/src/debo/drag_and_drop.rs +fn drop_target( + ui: &mut egui::Ui, + body: impl FnOnce(&mut egui::Ui) -> R, +) -> egui::InnerResponse { + let is_being_dragged = ui.memory(|mem| mem.is_anything_being_dragged()); + + let margin = egui::Vec2::splat(ui.visuals().clip_rect_margin); // 3.0 + + let background_id = ui.painter().add(egui::Shape::Noop); + + let avalibile_rect = ui.available_rect_before_wrap(); + let inner_rect = avalibile_rect.shrink2(margin); + let mut content_ui = ui.child_ui(inner_rect, *ui.layout()); + let ret = body(&mut content_ui); + + let outer_rect = + egui::Rect::from_min_max(avalibile_rect.min, content_ui.min_rect().max + margin); + let (rect, response) = ui.allocate_at_least(outer_rect.size(), egui::Sense::hover()); + + let style = if is_being_dragged && response.hovered() { + ui.visuals().widgets.active + } else { + ui.visuals().widgets.inactive + }; + + let fill = style.bg_fill; + let stroke = style.bg_stroke; + + ui.painter().set( + background_id, + egui::epaint::RectShape::new(rect, style.rounding, fill, stroke), + ); + + egui::InnerResponse::new(ret, response) +} + /// This will make the content as a popup if cannot has his own native window fn show_as_popup(ctx: &egui::Context, name: &str, content: impl FnOnce(&mut egui::Ui)) { if ctx.viewport_id() == ctx.parent_viewport_id() { @@ -258,7 +318,7 @@ fn show_as_popup(ctx: &egui::Context, name: &str, content: impl FnOnce(&mut egui } } -fn ui_info(ui: &mut egui::Ui) { +fn generic_ui(ui: &mut egui::Ui, container_id: impl Into) { let ctx = ui.ctx().clone(); ui.label(format!("Frame: {}", ctx.frame_nr())); ui.label(format!("Current Viewport Id: {}", ctx.viewport_id())); @@ -275,6 +335,122 @@ fn ui_info(ui: &mut egui::Ui) { outer_rect.min, outer_rect.size() )); + + let ctx = ui.ctx().clone(); + if ctx.viewport_id() == ctx.parent_viewport_id() { + let parent = ctx.parent_viewport_id(); + if ui.button("Set parent pos 0,0").clicked() { + ctx.viewport_command_for( + parent, + egui::ViewportCommand::OuterPosition(egui::pos2(0.0, 0.0)), + ); + } + } + + use std::collections::HashMap; + use std::sync::OnceLock; + + let container_id = container_id.into(); + + const COLS: usize = 2; + static DATA: OnceLock> = OnceLock::new(); + let data = DATA.get_or_init(Default::default); + data.write().init(container_id); + + #[derive(Default)] + struct DragAndDrop { + containers_data: HashMap>>, + data: HashMap, + counter: usize, + is_dragged: Option, + } + + impl DragAndDrop { + fn init(&mut self, container: Id) { + if !self.containers_data.contains_key(&container) { + for i in 0..COLS { + self.insert( + container, + i, + format!("From: {container:?}, and is: {}", self.counter), + ); + } + } + } + fn insert(&mut self, container: Id, col: usize, value: impl Into) { + assert!(col <= COLS, "The coll should be less then: {COLS}"); + + let value: String = value.into(); + let id = Id::new(format!("%{}% {}", self.counter, &value)); + self.data.insert(id, value); + let viewport_data = self.containers_data.entry(container).or_insert_with(|| { + let mut res = Vec::new(); + res.resize_with(COLS, Default::default); + res + }); + self.counter += 1; + + viewport_data[col].push(id); + } + + fn cols(&self, container: Id, col: usize) -> Vec<(Id, String)> { + assert!(col <= COLS, "The col should be less then: {COLS}"); + let container_data = &self.containers_data[&container]; + container_data[col] + .iter() + .map(|id| (*id, self.data[id].clone())) + .collect() + } + + /// Move element ID to Viewport and col + fn mov(&mut self, to: Id, col: usize) { + let Some(id) = self.is_dragged.take() else {return}; + assert!(col <= COLS, "The col should be less then: {COLS}"); + + // Should be a better way to do this! + for container_data in self.containers_data.values_mut() { + for ids in container_data { + ids.retain(|i| *i != id); + } + } + + if let Some(container_data) = self.containers_data.get_mut(&to) { + container_data[col].push(id); + } + } + + fn dragging(&mut self, id: Id) { + self.is_dragged = Some(id); + } + } + + ui.separator(); + ui.label("Drag and drop:"); + ui.columns(COLS, |ui| { + for col in 0..COLS { + let data = DATA.get().unwrap(); + let ui = &mut ui[col]; + let mut is_dragged = None; + let res = drop_target(ui, |ui| { + ui.set_min_height(60.0); + for (id, value) in data.read().cols(container_id, col) { + drag_source(ui, id, |ui| { + ui.add(egui::Label::new(value).sense(egui::Sense::click())); + if ui.memory(|mem| mem.is_being_dragged(id)) { + is_dragged = Some(id); + } + }); + } + }); + if let Some(id) = is_dragged { + data.write().dragging(id); + } + if res.response.hovered() && ui.input(|i| i.pointer.any_released()) { + data.write().mov(container_id, col); + } + } + }); + ui.separator(); } fn main() { @@ -286,7 +462,7 @@ fn main() { #[cfg(feature = "wgpu")] renderer: eframe::Renderer::Wgpu, - initial_window_size: Some(egui::Vec2::new(400.0, 220.0)), + initial_window_size: Some(egui::Vec2::new(450.0, 300.0)), ..NativeOptions::default() }, Box::new(|_| Box::::default()),