[demos] Add drag-and-drop demo (+ dancing strings demo)

This commit is contained in:
Emil Ernerfeldt 2020-11-02 17:53:28 +01:00
parent 4c25465e57
commit d4d59d94b9
12 changed files with 370 additions and 31 deletions

View file

@ -66,7 +66,7 @@ impl State {
let openness = self.openness(ui.ctx(), id); let openness = self.openness(ui.ctx(), id);
let animate = 0.0 < openness && openness < 1.0; let animate = 0.0 < openness && openness < 1.0;
if animate { if animate {
Some(ui.add_custom(|child_ui| { Some(ui.wrap(|child_ui| {
let max_height = if self.open { let max_height = if self.open {
if let Some(full_height) = self.open_height { if let Some(full_height) = self.open_height {
remap_clamp(openness, 0.0..=1.0, 0.0..=full_height) remap_clamp(openness, 0.0..=1.0, 0.0..=full_height)
@ -96,7 +96,7 @@ impl State {
r r
})) }))
} else if self.open || ui.memory().all_collpasing_are_open { } else if self.open || ui.memory().all_collpasing_are_open {
let (ret, response) = ui.add_custom(add_contents); let (ret, response) = ui.wrap(add_contents);
let full_size = response.rect.size(); let full_size = response.rect.size();
self.open_height = Some(full_size.y); self.open_height = Some(full_size.y);
Some((ret, response)) Some((ret, response))

View file

@ -31,6 +31,16 @@ impl Frame {
} }
} }
/// dark canvas to draw on
pub fn dark_canvas(style: &Style) -> Self {
Self {
margin: Vec2::new(10.0, 10.0),
corner_radius: 5.0,
fill: Srgba::black_alpha(250),
stroke: style.visuals.widgets.noninteractive.bg_stroke,
}
}
/// Suitable for a fullscreen app /// Suitable for a fullscreen app
pub fn background(style: &Style) -> Self { pub fn background(style: &Style) -> Self {
Self { Self {

View file

@ -39,7 +39,7 @@ impl ColorTest {
ui.heading("sRGB color test"); ui.heading("sRGB color test");
ui.label("Use a color picker to ensure this color is (255, 165, 0) / #ffa500"); ui.label("Use a color picker to ensure this color is (255, 165, 0) / #ffa500");
ui.add_custom(|ui| { ui.wrap(|ui| {
ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients
let g = Gradient::one_color(Srgba::new(255, 165, 0, 255)); let g = Gradient::one_color(Srgba::new(255, 165, 0, 255));
self.vertex_gradient(ui, "orange rgb(255, 165, 0) - vertex", WHITE, &g); self.vertex_gradient(ui, "orange rgb(255, 165, 0) - vertex", WHITE, &g);
@ -55,7 +55,7 @@ impl ColorTest {
ui.separator(); ui.separator();
ui.label("Test that vertex color times texture color is done in linear space:"); ui.label("Test that vertex color times texture color is done in linear space:");
ui.add_custom(|ui| { ui.wrap(|ui| {
ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients
let tex_color = Rgba::new(1.0, 0.25, 0.25, 1.0); let tex_color = Rgba::new(1.0, 0.25, 0.25, 1.0);
@ -152,7 +152,7 @@ impl ColorTest {
show_color(ui, right, color_size); show_color(ui, right, color_size);
}); });
ui.add_custom(|ui| { ui.wrap(|ui| {
ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients
if is_opaque { if is_opaque {
let g = Gradient::ground_truth_linear_gradient(left, right); let g = Gradient::ground_truth_linear_gradient(left, right);

View file

@ -0,0 +1,69 @@
use std::sync::Arc;
use crate::{containers::*, demos::*, *};
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct DancingStrings {}
impl Default for DancingStrings {
fn default() -> Self {
Self {}
}
}
impl Demo for DancingStrings {
fn name(&self) -> &str {
"Dancing Strings"
}
fn show(&mut self, ctx: &Arc<Context>, open: &mut bool) {
Window::new(self.name())
.open(open)
.default_size(vec2(512.0, 256.0))
.scroll(false)
.show(ctx, |ui| self.ui(ui));
}
}
impl View for DancingStrings {
fn ui(&mut self, ui: &mut Ui) {
Frame::dark_canvas(ui.style()).show(ui, |ui| {
ui.ctx().request_repaint();
let time = ui.input().time;
let desired_size = ui.available().width() * vec2(1.0, 0.35);
let rect = ui.allocate_space(desired_size);
let mut cmds = vec![];
for &mode in &[2, 3, 5] {
let mode = mode as f32;
let n = 120;
let speed = 1.5;
let points: Vec<Pos2> = (0..=n)
.map(|i| {
let t = i as f32 / (n as f32);
let amp = (time as f32 * speed * mode).sin() / mode;
let y = amp * (t * math::TAU / 2.0 * mode).sin();
pos2(
lerp(rect.x_range(), t),
remap(y, -1.0..=1.0, rect.y_range()),
)
})
.collect();
let thickness = 10.0 / mode;
cmds.push(paint::PaintCmd::line(
points,
Stroke::new(thickness, Srgba::additive_luminance(196)),
));
}
ui.painter().extend(cmds);
});
ui.add(__egui_github_link_file!());
}
}

View file

@ -1,6 +1,10 @@
use std::sync::Arc; use std::sync::Arc;
use crate::{app, demos, Context, Id, Resize, ScrollArea, Ui, Window}; use crate::{
app,
demos::{self, Demo},
Context, Id, Resize, ScrollArea, Ui, Window,
};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -22,6 +26,39 @@ pub struct DemoEnvironment {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
struct Demos {
/// open, view
#[serde(skip)] // TODO
demos: Vec<(bool, Box<dyn Demo>)>,
}
impl Default for Demos {
fn default() -> Self {
Self {
demos: vec![
(false, Box::new(crate::demos::DancingStrings::default())),
(false, Box::new(crate::demos::DragAndDropDemo::default())),
],
}
}
}
impl Demos {
pub fn checkboxes(&mut self, ui: &mut Ui) {
for (ref mut open, demo) in &mut self.demos {
ui.checkbox(open, demo.name());
}
}
pub fn show(&mut self, ctx: &Arc<Context>) {
for (ref mut open, demo) in &mut self.demos {
demo.show(ctx, open);
}
}
}
// ----------------------------------------------------------------------------
/// A menu bar in which you can select different demo windows to show. /// A menu bar in which you can select different demo windows to show.
#[derive(Default)] #[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
@ -36,6 +73,9 @@ pub struct DemoWindows {
fractal_clock: demos::FractalClock, fractal_clock: demos::FractalClock,
/// open, title, view
demos: Demos,
#[cfg_attr(feature = "serde", serde(skip))] #[cfg_attr(feature = "serde", serde(skip))]
previous_link: Option<DemoLink>, previous_link: Option<DemoLink>,
} }
@ -78,7 +118,8 @@ impl DemoWindows {
ui.heading("Windows:"); ui.heading("Windows:");
ui.indent("windows", |ui| { ui.indent("windows", |ui| {
self.open_windows.ui(ui); self.open_windows.checkboxes(ui);
self.demos.checkboxes(ui);
}); });
}); });
@ -101,6 +142,7 @@ impl DemoWindows {
demo_window, demo_window,
color_test, color_test,
fractal_clock, fractal_clock,
demos,
.. ..
} = self; } = self;
@ -139,6 +181,8 @@ impl DemoWindows {
color_test.ui(ui, tex_allocator); color_test.ui(ui, tex_allocator);
}); });
demos.show(ctx);
fractal_clock.window( fractal_clock.window(
ctx, ctx,
&mut open_windows.fractal_clock, &mut open_windows.fractal_clock,
@ -243,7 +287,7 @@ impl OpenWindows {
} }
} }
fn ui(&mut self, ui: &mut Ui) { fn checkboxes(&mut self, ui: &mut Ui) {
let Self { let Self {
demo, demo,
fractal_clock, fractal_clock,
@ -253,16 +297,19 @@ impl OpenWindows {
resize, resize,
color_test, color_test,
} = self; } = self;
ui.checkbox(demo, "Demo"); ui.label("Egui:");
ui.checkbox(fractal_clock, "Fractal Clock");
ui.separator();
ui.checkbox(settings, "Settings"); ui.checkbox(settings, "Settings");
ui.checkbox(inspection, "Inspection"); ui.checkbox(inspection, "Inspection");
ui.checkbox(memory, "Memory"); ui.checkbox(memory, "Memory");
ui.checkbox(resize, "Resize examples");
ui.separator(); ui.separator();
ui.checkbox(demo, "Demo");
ui.separator();
ui.checkbox(resize, "Resize examples");
ui.checkbox(color_test, "Color test") ui.checkbox(color_test, "Color test")
.on_hover_text("For testing the integrations painter"); .on_hover_text("For testing the integrations painter");
ui.separator();
ui.label("Misc:");
ui.checkbox(fractal_clock, "Fractal Clock");
} }
} }
@ -282,7 +329,7 @@ fn show_menu_bar(ui: &mut Ui, windows: &mut OpenWindows, seconds_since_midnight:
*ui.ctx().memory() = Default::default(); *ui.ctx().memory() = Default::default();
} }
}); });
menu::menu(ui, "Windows", |ui| windows.ui(ui)); menu::menu(ui, "Windows", |ui| windows.checkboxes(ui));
menu::menu(ui, "About", |ui| { menu::menu(ui, "About", |ui| {
ui.label("This is Egui"); ui.label("This is Egui");
ui.add(Hyperlink::new("https://github.com/emilk/egui").text("Egui home page")); ui.add(Hyperlink::new("https://github.com/emilk/egui").text("Egui home page"));

View file

@ -0,0 +1,161 @@
use crate::{
demos::{Demo, View},
*,
};
pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) {
let is_being_dragged = ui.memory().is_being_dragged(id);
if !is_being_dragged {
let response = ui.wrap(body).1;
// Check for drags:
let response = ui.interact(response.rect, id, Sense::drag());
if response.hovered {
ui.output().cursor_icon = CursorIcon::Grab;
}
} else {
ui.output().cursor_icon = CursorIcon::Grabbing;
// Paint the body to a new layer:
let layer_id = LayerId::new(layers::Order::Tooltip, id);
let response = ui.with_layer_id(layer_id, body).1;
// Now we move the visuals of the body to where the mouse is.
// Normally you need to decide a location for a widget first,
// because otherwise that widget cannot interact with the mouse.
// However, a dragged component cannot be interacted with anyway
// (anything with `Order::Tooltip` always gets an empty `Response`)
// So this is fine!
if let Some(mouse_pos) = ui.input().mouse.pos {
let delta = mouse_pos - response.rect.center();
ui.ctx().graphics().list(layer_id).translate(delta);
}
}
}
pub fn drop_target<R>(
ui: &mut Ui,
can_accept_what_is_being_dragged: bool,
body: impl FnOnce(&mut Ui) -> R,
) -> (R, Response) {
let is_being_dragged = ui.memory().is_anything_being_dragged();
let margin = Vec2::splat(4.0);
let outer_rect_bounds = ui.available();
let inner_rect = outer_rect_bounds.shrink2(margin);
let where_to_put_background = ui.painter().add(PaintCmd::Noop);
let mut content_ui = ui.child_ui(inner_rect, *ui.layout());
let ret = body(&mut content_ui);
let outer_rect = Rect::from_min_max(outer_rect_bounds.min, content_ui.min_rect().max + margin);
let response = ui.interact_hover(outer_rect);
let style = if is_being_dragged && can_accept_what_is_being_dragged && response.hovered {
ui.style().visuals.widgets.active
} else if is_being_dragged && can_accept_what_is_being_dragged {
ui.style().visuals.widgets.inactive
} else if is_being_dragged && !can_accept_what_is_being_dragged {
ui.style().visuals.widgets.disabled
} else {
ui.style().visuals.widgets.inactive
};
ui.painter().set(
where_to_put_background,
PaintCmd::Rect {
corner_radius: style.corner_radius,
fill: style.bg_fill,
stroke: style.bg_stroke,
rect: outer_rect,
},
);
ui.allocate_space(outer_rect.size());
(ret, response)
}
pub struct DragAndDropDemo {
/// columns with items
columns: Vec<Vec<&'static str>>,
}
impl Default for DragAndDropDemo {
fn default() -> Self {
Self {
columns: vec![
vec!["Item A", "Item B", "Item C"],
vec!["Item D", "Item E"],
vec!["Item F", "Item G", "Item H"],
],
}
}
}
impl Demo for DragAndDropDemo {
fn name(&self) -> &str {
"Drag and Drop"
}
fn show(&mut self, ctx: &std::sync::Arc<Context>, open: &mut bool) {
Window::new(self.name())
.open(open)
.default_size(vec2(256.0, 256.0))
.scroll(false)
.resizable(false)
.show(ctx, |ui| self.ui(ui));
}
}
impl View for DragAndDropDemo {
fn ui(&mut self, ui: &mut Ui) {
ui.label("This is a proof-of-concept of drag-and-drop in Egui");
ui.label("Drag items between columns.");
let mut source_col_row = None;
let mut drop_col = None;
ui.columns(self.columns.len(), |uis| {
for (col_idx, column) in self.columns.iter().enumerate() {
let ui = &mut uis[col_idx];
let can_accept_what_is_being_dragged = true; // We accept anything being dragged (for now) ¯\_(ツ)_/¯
let response = drop_target(ui, can_accept_what_is_being_dragged, |ui| {
ui.set_min_size(vec2(64.0, 100.0));
for (row_idx, &item) in column.iter().enumerate() {
let item_id = Id::new("item").with(col_idx).with(row_idx);
drag_source(ui, item_id, |ui| {
ui.label(item);
});
let this_item_being_dragged = ui.memory().is_being_dragged(item_id);
if this_item_being_dragged {
source_col_row = Some((col_idx, row_idx));
}
}
})
.1;
let is_being_dragged = ui.memory().is_anything_being_dragged();
if is_being_dragged && can_accept_what_is_being_dragged && response.hovered {
drop_col = Some(col_idx);
}
}
});
if let Some((source_col, source_row)) = source_col_row {
if let Some(drop_col) = drop_col {
if ui.input().mouse.released {
// do the drop:
let item = self.columns[source_col].remove(source_row);
self.columns[drop_col].push(item);
}
}
}
ui.add(__egui_github_link_file!());
}
}

View file

@ -39,7 +39,7 @@ impl FractalClock {
) { ) {
Window::new("FractalClock") Window::new("FractalClock")
.open(open) .open(open)
.default_rect(ctx.available_rect().expand(-42.0)) .default_size(vec2(512.0, 512.0))
.scroll(false) .scroll(false)
// Dark background frame to make it pop: // Dark background frame to make it pop:
.frame(Frame::window(&ctx.style()).fill(Srgba::black_alpha(250))) .frame(Frame::window(&ctx.style()).fill(Srgba::black_alpha(250)))

View file

@ -3,16 +3,19 @@
//! The demo-code is also used in benchmarks and tests. //! The demo-code is also used in benchmarks and tests.
mod app; mod app;
mod color_test; mod color_test;
mod dancing_strings;
pub mod demo_window; pub mod demo_window;
mod demo_windows; mod demo_windows;
mod drag_and_drop;
mod fractal_clock; mod fractal_clock;
mod sliders; mod sliders;
pub mod toggle_switch; pub mod toggle_switch;
mod widgets; mod widgets;
pub use { pub use {
app::*, color_test::ColorTest, demo_window::DemoWindow, demo_windows::*, app::*, color_test::ColorTest, dancing_strings::DancingStrings, demo_window::DemoWindow,
fractal_clock::FractalClock, sliders::Sliders, widgets::Widgets, demo_windows::*, drag_and_drop::*, fractal_clock::FractalClock, sliders::Sliders,
widgets::Widgets,
}; };
pub const LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; pub const LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
@ -23,6 +26,21 @@ Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, tur
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/// Something to view in the demo windows
pub trait View {
fn ui(&mut self, ui: &mut crate::Ui);
}
/// Something to view
pub trait Demo {
fn name(&self) -> &str;
/// Show windows, etc
fn show(&mut self, ctx: &std::sync::Arc<crate::Context>, open: &mut bool);
}
// ----------------------------------------------------------------------------
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub fn has_debug_assertions() -> bool { pub fn has_debug_assertions() -> bool {
true true

View file

@ -149,6 +149,14 @@ impl Memory {
self.interaction.kb_focus_id = None; self.interaction.kb_focus_id = None;
} }
pub fn is_anything_being_dragged(&self) -> bool {
self.interaction.drag_id.is_some()
}
pub fn is_being_dragged(&self, id: Id) -> bool {
self.interaction.drag_id == Some(id)
}
/// Forget window positions, sizes etc. /// Forget window positions, sizes etc.
/// Can be used to auto-layout windows. /// Can be used to auto-layout windows.
pub fn reset_areas(&mut self) { pub fn reset_areas(&mut self) {

View file

@ -32,6 +32,20 @@ impl Painter {
} }
} }
#[must_use]
pub fn with_layer_id(self, layer_id: LayerId) -> Self {
Self {
ctx: self.ctx,
layer_id,
clip_rect: self.clip_rect,
}
}
/// redirect
pub fn set_layer_id(&mut self, layer_id: LayerId) {
self.layer_id = layer_id;
}
/// Create a painter for a sub-region of this `Painter`. /// Create a painter for a sub-region of this `Painter`.
/// ///
/// The clip-rect of the returned `Painter` will be the intersection /// The clip-rect of the returned `Painter` will be the intersection

View file

@ -659,12 +659,26 @@ impl Ui {
/// # Adding Containers / Sub-uis: /// # Adding Containers / Sub-uis:
impl Ui { impl Ui {
pub fn collapsing<R>( /// Create a child ui. You can use this to temporarily change the Style of a sub-region, for instance.
pub fn wrap<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Response) {
let child_rect = self.available();
let mut child_ui = self.child_ui(child_rect, self.layout);
let ret = add_contents(&mut child_ui);
let size = child_ui.min_size();
let rect = self.allocate_space(size);
(ret, self.interact_hover(rect))
}
/// Redirect paint commands to another paint layer.
pub fn with_layer_id<R>(
&mut self, &mut self,
heading: impl Into<String>, layer_id: LayerId,
add_contents: impl FnOnce(&mut Ui) -> R, add_contents: impl FnOnce(&mut Self) -> R,
) -> CollapsingResponse<R> { ) -> (R, Response) {
CollapsingHeader::new(heading).show(self, add_contents) self.wrap(|ui| {
ui.painter.set_layer_id(layer_id);
add_contents(ui)
})
} }
/// Create a child ui at the current cursor. /// Create a child ui at the current cursor.
@ -682,14 +696,12 @@ impl Ui {
self.allocate_space(child_ui.min_size()) self.allocate_space(child_ui.min_size())
} }
/// Create a child ui. You can use this to temporarily change the Style of a sub-region, for instance. pub fn collapsing<R>(
pub fn add_custom<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Response) { &mut self,
let child_rect = self.available(); heading: impl Into<String>,
let mut child_ui = self.child_ui(child_rect, self.layout); add_contents: impl FnOnce(&mut Ui) -> R,
let ret = add_contents(&mut child_ui); ) -> CollapsingResponse<R> {
let size = child_ui.min_size(); CollapsingHeader::new(heading).show(self, add_contents)
let rect = self.allocate_space(size);
(ret, self.interact_hover(rect))
} }
/// Create a child ui which is indented to the right /// Create a child ui which is indented to the right
@ -786,7 +798,7 @@ impl Ui {
self.with_layout(Layout::vertical(Align::Min), add_contents) self.with_layout(Layout::vertical(Align::Min), add_contents)
} }
pub fn inner_layout<R>( fn inner_layout<R>(
&mut self, &mut self,
layout: Layout, layout: Layout,
initial_size: Vec2, initial_size: Vec2,