[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 animate = 0.0 < openness && openness < 1.0;
if animate {
Some(ui.add_custom(|child_ui| {
Some(ui.wrap(|child_ui| {
let max_height = if self.open {
if let Some(full_height) = self.open_height {
remap_clamp(openness, 0.0..=1.0, 0.0..=full_height)
@ -96,7 +96,7 @@ impl State {
r
}))
} 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();
self.open_height = Some(full_size.y);
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
pub fn background(style: &Style) -> Self {
Self {

View file

@ -39,7 +39,7 @@ impl ColorTest {
ui.heading("sRGB color test");
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
let g = Gradient::one_color(Srgba::new(255, 165, 0, 255));
self.vertex_gradient(ui, "orange rgb(255, 165, 0) - vertex", WHITE, &g);
@ -55,7 +55,7 @@ impl ColorTest {
ui.separator();
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
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);
});
ui.add_custom(|ui| {
ui.wrap(|ui| {
ui.style_mut().spacing.item_spacing.y = 0.0; // No spacing between gradients
if is_opaque {
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 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.
#[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
@ -36,6 +73,9 @@ pub struct DemoWindows {
fractal_clock: demos::FractalClock,
/// open, title, view
demos: Demos,
#[cfg_attr(feature = "serde", serde(skip))]
previous_link: Option<DemoLink>,
}
@ -78,7 +118,8 @@ impl DemoWindows {
ui.heading("Windows:");
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,
color_test,
fractal_clock,
demos,
..
} = self;
@ -139,6 +181,8 @@ impl DemoWindows {
color_test.ui(ui, tex_allocator);
});
demos.show(ctx);
fractal_clock.window(
ctx,
&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 {
demo,
fractal_clock,
@ -253,16 +297,19 @@ impl OpenWindows {
resize,
color_test,
} = self;
ui.checkbox(demo, "Demo");
ui.checkbox(fractal_clock, "Fractal Clock");
ui.separator();
ui.label("Egui:");
ui.checkbox(settings, "Settings");
ui.checkbox(inspection, "Inspection");
ui.checkbox(memory, "Memory");
ui.checkbox(resize, "Resize examples");
ui.separator();
ui.checkbox(demo, "Demo");
ui.separator();
ui.checkbox(resize, "Resize examples");
ui.checkbox(color_test, "Color test")
.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();
}
});
menu::menu(ui, "Windows", |ui| windows.ui(ui));
menu::menu(ui, "Windows", |ui| windows.checkboxes(ui));
menu::menu(ui, "About", |ui| {
ui.label("This is Egui");
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")
.open(open)
.default_rect(ctx.available_rect().expand(-42.0))
.default_size(vec2(512.0, 512.0))
.scroll(false)
// Dark background frame to make it pop:
.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.
mod app;
mod color_test;
mod dancing_strings;
pub mod demo_window;
mod demo_windows;
mod drag_and_drop;
mod fractal_clock;
mod sliders;
pub mod toggle_switch;
mod widgets;
pub use {
app::*, color_test::ColorTest, demo_window::DemoWindow, demo_windows::*,
fractal_clock::FractalClock, sliders::Sliders, widgets::Widgets,
app::*, color_test::ColorTest, dancing_strings::DancingStrings, demo_window::DemoWindow,
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.";
@ -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)]
pub fn has_debug_assertions() -> bool {
true

View file

@ -67,7 +67,7 @@ impl Sliders {
.text("f64 demo slider"),
);
ui.label("Sliders will automatically figure out how many decimals to show.");
ui.label("Sliders will automatically figure out how many decimals to show.");
if ui.button("Assign PI").clicked {
self.value = std::f64::consts::PI;

View file

@ -149,6 +149,14 @@ impl Memory {
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.
/// Can be used to auto-layout windows.
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`.
///
/// The clip-rect of the returned `Painter` will be the intersection

View file

@ -659,12 +659,26 @@ impl Ui {
/// # Adding Containers / Sub-uis:
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,
heading: impl Into<String>,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> CollapsingResponse<R> {
CollapsingHeader::new(heading).show(self, add_contents)
layer_id: LayerId,
add_contents: impl FnOnce(&mut Self) -> R,
) -> (R, Response) {
self.wrap(|ui| {
ui.painter.set_layer_id(layer_id);
add_contents(ui)
})
}
/// Create a child ui at the current cursor.
@ -682,14 +696,12 @@ impl Ui {
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 add_custom<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))
pub fn collapsing<R>(
&mut self,
heading: impl Into<String>,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> CollapsingResponse<R> {
CollapsingHeader::new(heading).show(self, add_contents)
}
/// 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)
}
pub fn inner_layout<R>(
fn inner_layout<R>(
&mut self,
layout: Layout,
initial_size: Vec2,