From c044ea3ff7796398b56c151ab5c163f0c91240fa Mon Sep 17 00:00:00 2001 From: xarvic Date: Tue, 30 Aug 2022 20:37:39 +0200 Subject: [PATCH 01/14] implement ViewportHeader --- druid/examples/viewport_header.rs | 96 ++++++++++++ druid/src/widget/flex.rs | 70 ++++++++- druid/src/widget/mod.rs | 4 +- druid/src/widget/viewport_header.rs | 224 ++++++++++++++++++++++++++++ 4 files changed, 392 insertions(+), 2 deletions(-) create mode 100755 druid/examples/viewport_header.rs create mode 100644 druid/src/widget/viewport_header.rs diff --git a/druid/examples/viewport_header.rs b/druid/examples/viewport_header.rs new file mode 100755 index 0000000000..e8f8738681 --- /dev/null +++ b/druid/examples/viewport_header.rs @@ -0,0 +1,96 @@ +// Copyright 2019 The Druid Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Shows a scroll widget, and also demonstrates how widgets that paint +//! outside their bounds can specify their paint region. + +// On Windows platform, don't show a console when opening the app. +#![windows_subsystem = "windows"] + +use druid::lens::Unit; +use druid::widget::prelude::*; +use druid::widget::{BackgroundBrush, Button, ClipBox, Flex, Label, List, Side, Slider, Tabs, TextBox, ViewportHeader}; +use druid::{AppLauncher, Color, Data, Insets, Lens, LocalizedString, Point, Rect, RoundedRectRadii, Vec2, WidgetExt, WidgetPod, WindowDesc}; +use im::Vector; +use std::sync::Arc; + +#[derive(Clone, Data, Lens)] +struct AppData { + list: Vector, +} + +#[derive(Clone, Data, Lens)] +struct Contact { + name: Arc, + info: Vector>, +} + +pub fn main() { + let window = WindowDesc::new(build_widget()) + .title(LocalizedString::new("scroll-demo-window-title").with_placeholder("Scroll demo")); + + let mut list = Vector::new(); + list.push_back(Arc::new("test".to_string())); + list.push_back(Arc::new("test2".to_string())); + list.push_back(Arc::new("test3".to_string())); + + AppLauncher::with_window(window) + .log_to_console() + .launch(AppData { list: Vector::new()}) + .expect("launch failed"); +} + +fn build_widget() -> impl Widget { + let list = List::new(||{ + + let body = Flex::column() + .with_child(Label::new("Name:")) + .with_default_spacer() + .with_child(TextBox::new().lens(Contact::name)) + .with_default_spacer() + .with_default_spacer() + .with_child(Label::new("Info:")) + .with_default_spacer() + .with_child(List::new(||TextBox::new().padding(Insets::from(5.0))).lens(Contact::info)) + .with_default_spacer() + .with_child(Button::new("Add Info").on_click(|_, data: &mut Contact, _|data.info.push_back(Arc::new(String::new())))) + .align_left() + .fix_width(150.0) + .padding(Insets::uniform_xy(25.0, 0.0)) + .background(Color::grey8(15)) + .rounded(RoundedRectRadii::new(0.0, 0.0, 5.0, 5.0)); + + ViewportHeader::new( + body, + Label::dynamic(|data: &Contact, _|format!("Contact \"{}\"", &data.name)) + .center() + .background(Color::BLACK) + .rounded(RoundedRectRadii::new(5.0, 5.0, 0.0, 0.0)), + Side::Top, + ) + .clipped_content(true) + .with_minimum_visible_content(20.0) + .padding(Insets::uniform_xy(0.0, 5.0)) + }) + .lens(AppData::list) + .scroll(); + + Flex::column() + .with_flex_child(list, 1.0) + .with_default_spacer() + .with_child(Button::new("Add Contact").on_click(|_, data: &mut AppData, _|data.list.push_back(Contact { + name: Arc::new("New Contact".to_string()), + info: Default::default(), + }))) +} diff --git a/druid/src/widget/flex.rs b/druid/src/widget/flex.rs index 9fd9312828..2adc0e4512 100644 --- a/druid/src/widget/flex.rs +++ b/druid/src/widget/flex.rs @@ -19,7 +19,7 @@ use std::ops::Add; use crate::debug_state::DebugState; use crate::kurbo::{common::FloatExt, Vec2}; use crate::widget::prelude::*; -use crate::{Data, KeyOrValue, Point, Rect, WidgetPod}; +use crate::{Data, Insets, KeyOrValue, Point, Rect, WidgetPod}; use tracing::{instrument, trace}; /// A container with either horizontal or vertical layout. @@ -197,6 +197,74 @@ pub enum Axis { Vertical, } +#[derive(Data, Debug, Clone, Copy, PartialEq, Eq)] +pub enum Orientation { + Start, + End, +} + +impl Orientation { + pub fn order(&self, reference: T, side: T) -> (T, T) { + match self { + Orientation::Start => (side, reference), + Orientation::End => (reference, side), + } + } +} + +#[derive(Data, Debug, Clone, Copy, PartialEq, Eq)] +pub enum Side { + Top, + Left, + Right, + Bottom, +} + +impl Side { + pub fn axis(&self) -> Axis { + match self { + Side::Top | Side::Bottom => Axis::Vertical, + Side::Left | Side::Right => Axis::Horizontal, + } + } + + pub fn orientation(&self) -> Orientation { + match self { + Side::Top | Side::Left => Orientation::Start, + Side::Bottom | Side::Right => Orientation::End, + } + } + + pub fn direction(&self) -> Vec2 { + match self { + Side::Top => Vec2::new(0.0, -1.0), + Side::Bottom => Vec2::new(0.0, 1.0), + Side::Left => Vec2::new(-1.0, 0.0), + Side::Right => Vec2::new(1.0, 0.0), + } + } + + pub fn from_inset(&self, insets: Insets) -> f64 { + match self { + Side::Top => insets.y0, + Side::Left => insets.x0, + Side::Right => insets.x1, + Side::Bottom => insets.y1, + } + } + + pub fn as_insets(&self, amount: f64) -> Insets { + let mut insets = Insets::ZERO; + match self { + Side::Top => {insets.y0 = amount;} + Side::Left => {insets.x0 = amount;} + Side::Right => {insets.x1 = amount;} + Side::Bottom => {insets.y1 = amount;} + } + insets + } +} + impl Axis { /// Get the axis perpendicular to this one. pub fn cross(self) -> Axis { diff --git a/druid/src/widget/mod.rs b/druid/src/widget/mod.rs index b791e3f9a4..340a5da7a9 100644 --- a/druid/src/widget/mod.rs +++ b/druid/src/widget/mod.rs @@ -64,6 +64,7 @@ mod view_switcher; mod widget; mod widget_ext; mod z_stack; +mod viewport_header; pub use self::image::Image; pub use added::Added; @@ -79,7 +80,7 @@ pub use controller::{Controller, ControllerHost}; pub use disable_if::DisabledIf; pub use either::Either; pub use env_scope::EnvScope; -pub use flex::{Axis, CrossAxisAlignment, Flex, FlexParams, MainAxisAlignment}; +pub use flex::{Axis, CrossAxisAlignment, Flex, FlexParams, MainAxisAlignment, Side, Orientation}; pub use identity_wrapper::IdentityWrapper; pub use intrinsic_width::IntrinsicWidth; pub use label::{Label, LabelText, LineBreaking, RawLabel}; @@ -105,6 +106,7 @@ pub use tabs::{TabInfo, Tabs, TabsEdge, TabsPolicy, TabsState, TabsTransition}; pub use textbox::TextBox; pub use value_textbox::{TextBoxEvent, ValidationDelegate, ValueTextBox}; pub use view_switcher::ViewSwitcher; +pub use viewport_header::{ViewportHeader, ViewportHeaderConfig}; #[doc(hidden)] pub use widget::{Widget, WidgetId}; #[doc(hidden)] diff --git a/druid/src/widget/viewport_header.rs b/druid/src/widget/viewport_header.rs new file mode 100644 index 0000000000..68cad4eebc --- /dev/null +++ b/druid/src/widget/viewport_header.rs @@ -0,0 +1,224 @@ +use druid::RenderContext; +use crate::{BoxConstraints, Color, Data, Env, Event, EventCtx, Insets, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, Rect, Size, UpdateCtx, ViewContext, Widget, WidgetExt, WidgetId, WidgetPod}; +use crate::commands::SCROLL_TO_VIEW; +use crate::widget::flex::{Orientation, Side}; + + +pub struct ViewportHeader { + header: WidgetPod>>, + content: WidgetPod>>, + + header_config: ViewportHeaderConfig, + clip_content: bool, +} + +pub struct ViewportHeaderConfig { + content_size: Size, + viewport: Rect, + header_side: Side, + header_size: f64, + minimum_visible_content: f64, +} + +impl ViewportHeaderConfig { + pub fn new(side: Side) -> Self { + Self { + content_size: Size::ZERO, + viewport: Rect::from_origin_size( + Point::ORIGIN, + Size::new(f64::INFINITY, f64::INFINITY), + ), + header_side: side, + header_size: 0.0, + minimum_visible_content: 0.0, + } + } + + pub fn size(&self) -> Size { + self.content_size + Size::from(self.header_side.axis().pack(self.header_size, 0.0)) + } + + pub fn side(&self) -> Side { + self.header_side + } + + /// The amount of pixels the header has be moved into the direction of content to stay inside + /// of the viewport. + /// + /// The max value of this function is `major_content_size - minimum_visible_content`. + /// Therefore header cant leave the content on the other side. + pub fn overlapping(&self) -> f64 { + //Compute Clipped area + let global_layout_rect = Rect::from_origin_size(Point::ZERO, self.size()); + let insets = global_layout_rect - self.viewport; + + //Compute max movable distance + let axis = self.header_side.axis(); + let max = axis.major(self.content_size) - self.minimum_visible_content; + + self.header_side.from_inset(insets).max(0.0).min(max) + } + + /// The amount of pixels inside the viewport with is overlapped by the header. + pub fn visual_overlapping(&self) -> f64 { + self.overlapping().min(self.header_size) + } + + /// Returns the origin of the content and of the header. + pub fn origins(&self) -> (Point, Point) { + let orientation = self.header_side.orientation(); + let axis = self.header_side.axis(); + + let (first, _) = orientation.order( + axis.major(self.content_size), + self.header_size + ); + + let (content_origin, header_origin) = orientation.order( + Point::ZERO, + Point::from(axis.pack(first, 0.0)) + ); + let header_origin = header_origin - self.header_side.direction() * self.overlapping(); + + (content_origin, header_origin) + } + + pub fn transform_content_scroll_to_view(&self, ctx: &mut EventCtx, rect: Rect) { + let axis = self.header_side.axis(); + // The length on the major axis with is overlapped by the header. + let overlapping = self.visual_overlapping(); + + if overlapping != 0.0 { + ctx.set_handled(); + + let new_rect = rect + self.header_side.direction() * overlapping; + ctx.submit_notification_without_warning(SCROLL_TO_VIEW.with(new_rect)); + } + } + + pub fn update_context(&mut self, view_context: ViewContext) { + self.viewport = view_context.clip; + } + + pub fn set_content_size(&mut self, content_size: Size) { + self.content_size = content_size; + } + + pub fn set_header_size(&mut self, header_size: Size) { + let axis = self.header_side.axis(); + self.header_size = axis.major(header_size); + } + + pub fn set_minimum_visible_content(&mut self, visible: f64) { + self.minimum_visible_content = visible; + } + +} + +impl ViewportHeader { + pub fn new(content: impl Widget + 'static, header: impl Widget + 'static, side: Side) -> Self { + Self { + header: WidgetPod::new(Box::new(header)), + content: WidgetPod::new(Box::new(content)), + header_config: ViewportHeaderConfig::new(side), + clip_content: false, + } + } + + pub fn with_minimum_visible_content(mut self, minimum_visible_content: f64) -> Self { + self.header_config.set_minimum_visible_content(minimum_visible_content); + self + } + + pub fn clipped_content(mut self, clipped_content: bool) -> Self { + self.clip_content = clipped_content; + self + } +} + +impl Widget for ViewportHeader { + fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) { + if let Event::Notification(notification) = event { + if let Some(rect) = notification.get(SCROLL_TO_VIEW) { + if notification.route() == self.content.id() { + self.header_config.transform_content_scroll_to_view(ctx, *rect); + } + return; + } + } + + self.header.event(ctx, event, data, env); + if self.header.is_hot() && event.is_pointer_event() { + ctx.set_handled(); + } + self.content.event(ctx, event, data, env); + } + + fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) { + match event { + LifeCycle::ViewContextChanged(view_context) => { + println!("update ctx"); + self.header_config.update_context(*view_context); + let (_, header_origin) = self.header_config.origins(); + + self.header.set_origin(ctx, data, env, header_origin); + self.header.lifecycle(ctx, event, data, env); + + let mut content_view_context = *view_context; + if self.header.is_hot() { + content_view_context.last_mouse_position = None; + } + content_view_context.clip = content_view_context.clip - + self.header_config.side().as_insets(self.header_config.visual_overlapping()); + + self.content.lifecycle(ctx, event, data, env); + } + LifeCycle::BuildFocusChain if self.header_config.side().orientation() == Orientation::End => { + self.content.lifecycle(ctx, event, data, env); + self.header.lifecycle(ctx, event, data, env); + } + _ => { + self.header.lifecycle(ctx, event, data, env); + self.content.lifecycle(ctx, event, data, env); + } + } + } + + fn update(&mut self, ctx: &mut UpdateCtx, _old_data: &T, data: &T, env: &Env) { + self.header.update(ctx, data, env); + self.content.update(ctx, data, env); + } + + fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size { + let axis = self.header_config.side().axis(); + + let content_size = self.content.layout(ctx, bc, data, env); + self.header_config.set_content_size(content_size); + let header_bc = BoxConstraints::new( + Size::from(axis.pack(0.0, axis.minor(content_size))), + Size::from(axis.pack(f64::INFINITY, axis.minor(content_size))), + ); + + let header_size = self.header.layout(ctx, &header_bc, data, env); + self.header_config.set_header_size(header_size); + + let (content_origin, header_origin) = self.header_config.origins(); + + self.header.set_origin(ctx, data, env, header_origin); + self.content.set_origin(ctx, data, env, content_origin); + + self.header_config.size() + } + + fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) { + ctx.with_save(|ctx| { + if self.clip_content { + let content_rect = self.content.layout_rect() - + self.header_config.side().as_insets(self.header_config.overlapping()); + ctx.clip(content_rect); + } + self.content.paint(ctx, data, env); + }); + self.header.paint(ctx, data, env); + } +} \ No newline at end of file From 8f2458783d9136bf43a253c13f5d35a612aea119 Mon Sep 17 00:00:00 2001 From: xarvic Date: Sun, 11 Sep 2022 16:38:07 +0200 Subject: [PATCH 02/14] added in_viewport option for Align widget --- druid/src/widget/align.rs | 70 +++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/druid/src/widget/align.rs b/druid/src/widget/align.rs index 277f52d402..4925dc2676 100644 --- a/druid/src/widget/align.rs +++ b/druid/src/widget/align.rs @@ -18,6 +18,7 @@ use crate::debug_state::DebugState; use crate::widget::prelude::*; use crate::{Data, Rect, Size, UnitPoint, WidgetPod}; use tracing::{instrument, trace}; +use crate::contexts::CommandCtx; /// A widget that aligns its child. pub struct Align { @@ -25,6 +26,9 @@ pub struct Align { child: WidgetPod>>, width_factor: Option, height_factor: Option, + in_viewport: bool, + viewport: Rect, + my_size: Size, } impl Align { @@ -39,6 +43,9 @@ impl Align { child: WidgetPod::new(child).boxed(), width_factor: None, height_factor: None, + in_viewport: false, + viewport: Rect::new(0.0, 0.0, f64::INFINITY, f64::INFINITY), + my_size: Size::ZERO, } } @@ -64,6 +71,9 @@ impl Align { child: WidgetPod::new(child).boxed(), width_factor: None, height_factor: Some(1.0), + in_viewport: false, + viewport: Rect::new(0.0, 0.0, f64::INFINITY, f64::INFINITY), + my_size: Size::ZERO, } } @@ -74,8 +84,47 @@ impl Align { child: WidgetPod::new(child).boxed(), width_factor: Some(1.0), height_factor: None, + in_viewport: false, + viewport: Rect::new(0.0, 0.0, f64::INFINITY, f64::INFINITY), + my_size: Size::ZERO, } } + + fn in_viewport(mut self) -> Self { + self.in_viewport = true; + self + } + + fn align(&mut self, ctx: &mut C, data: &T, env: &Env) { + let size = self.child.layout_rect().size(); + + let extra_width = (self.my_size.width - size.width).max(0.); + let extra_height = (self.my_size.height - size.height).max(0.); + + // The part of our layout_rect the origin of the child is alloed to be in + let mut extra_space = Rect::new(0., 0., extra_width, extra_height); + + if self.in_viewport { + // The part of the viewport the origin of the child is alloed to be in + let viewport = Rect::from_origin_size(self.viewport.origin(), self.viewport.size() - size); + + // Essentially Rect::intersect but this implementation chooses the point closed to viewport + // inside extra_space to give the child a valid origin + extra_space.x0 = extra_space.x0.max(viewport.x0).min(extra_space.x1); + extra_space.y0 = extra_space.y0.max(viewport.y0).min(extra_space.y1); + extra_space.x1 = extra_space.x1.min(viewport.x1).max(extra_space.x0); + extra_space.y1 = extra_space.y1.min(viewport.y1).max(extra_space.y0); + } + + let origin = self + .align + .resolve(extra_space) + .expand(); + self.child.set_origin(ctx, data, env, origin); + + let my_insets = self.child.compute_parent_paint_insets(my_size); + ctx.set_paint_insets(my_insets); + } } impl Widget for Align { @@ -86,6 +135,13 @@ impl Widget for Align { #[instrument(name = "Align", level = "trace", skip(self, ctx, event, data, env))] fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) { + if let LifeCycle::ViewContextChanged(view_ctx) = event { + self.viewport = view_ctx.clip; + if self.in_viewport { + self.align(ctx, data, env); + } + } + self.child.lifecycle(ctx, event, data, env) } @@ -118,21 +174,13 @@ impl Widget for Align { my_size.height = size.height * height; } - my_size = bc.constrain(my_size); - let extra_width = (my_size.width - size.width).max(0.); - let extra_height = (my_size.height - size.height).max(0.); - let origin = self - .align - .resolve(Rect::new(0., 0., extra_width, extra_height)) - .expand(); - self.child.set_origin(ctx, origin); + self.my_size = bc.constrain(my_size); + self.align(ctx, data, env); - let my_insets = self.child.compute_parent_paint_insets(my_size); - ctx.set_paint_insets(my_insets); if self.height_factor.is_some() { let baseline_offset = self.child.baseline_offset(); if baseline_offset > 0f64 { - ctx.set_baseline_offset(baseline_offset + extra_height / 2.0); + ctx.set_baseline_offset(self.my_size.height - self.child.layout_rect().y1 + baseline_offset); } } From 83f81b913b7d6729ce5a43d9667d50484d89f24f Mon Sep 17 00:00:00 2001 From: xarvic Date: Tue, 20 Sep 2022 20:40:04 +0200 Subject: [PATCH 03/14] quick safe --- druid/examples/z_stack_bug.rs | 43 +++++++++++++++++++++++++++++++++++ druid/src/widget/align.rs | 33 ++++++++++++--------------- 2 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 druid/examples/z_stack_bug.rs diff --git a/druid/examples/z_stack_bug.rs b/druid/examples/z_stack_bug.rs new file mode 100644 index 0000000000..cb492261f5 --- /dev/null +++ b/druid/examples/z_stack_bug.rs @@ -0,0 +1,43 @@ +use druid::{Color, Widget, RenderContext, WindowDesc, AppLauncher, WidgetExt, UnitPoint}; +use druid::widget::{CrossAxisAlignment, Flex, Label, Painter, Scroll, SizedBox, ZStack}; + +fn main() { + let window = WindowDesc::new(build_ui()); + + AppLauncher::with_window(window) + .log_to_console() + .launch(()) + .unwrap(); +} + +fn build_ui() -> impl Widget<()> { + let mut container = Flex::column() + .cross_axis_alignment(CrossAxisAlignment::Fill); + + for _ in 0..10 { + let stack = ZStack::new( + Label::new("Base layer") + .align_vertical(UnitPoint::TOP) + .expand_width() + .fix_height(200.0) + .background(Color::grey8(20)) + ) + .with_centered_child( + Label::new("Overlay") + .center() + .fix_height(100.0) + .background(Color::grey8(0)) + ); + + container.add_child(SizedBox::empty().height(200.0)); + container.add_child(Flex::row() + .with_flex_child(stack) + .with_default_spacer() + .with_child(SizedBox::empty()) + + ); + } + + Scroll::new(container) + .vertical() +} diff --git a/druid/src/widget/align.rs b/druid/src/widget/align.rs index 4925dc2676..704b42478c 100644 --- a/druid/src/widget/align.rs +++ b/druid/src/widget/align.rs @@ -28,7 +28,6 @@ pub struct Align { height_factor: Option, in_viewport: bool, viewport: Rect, - my_size: Size, } impl Align { @@ -45,7 +44,6 @@ impl Align { height_factor: None, in_viewport: false, viewport: Rect::new(0.0, 0.0, f64::INFINITY, f64::INFINITY), - my_size: Size::ZERO, } } @@ -73,7 +71,6 @@ impl Align { height_factor: Some(1.0), in_viewport: false, viewport: Rect::new(0.0, 0.0, f64::INFINITY, f64::INFINITY), - my_size: Size::ZERO, } } @@ -86,7 +83,6 @@ impl Align { height_factor: None, in_viewport: false, viewport: Rect::new(0.0, 0.0, f64::INFINITY, f64::INFINITY), - my_size: Size::ZERO, } } @@ -95,21 +91,22 @@ impl Align { self } - fn align(&mut self, ctx: &mut C, data: &T, env: &Env) { + fn align<'b, C: CommandCtx<'b>>(&mut self, ctx: &mut C, data: &T, env: &Env, my_size: Size) { let size = self.child.layout_rect().size(); - let extra_width = (self.my_size.width - size.width).max(0.); - let extra_height = (self.my_size.height - size.height).max(0.); + let extra_width = (my_size.width - size.width).max(0.); + let extra_height = (my_size.height - size.height).max(0.); - // The part of our layout_rect the origin of the child is alloed to be in + // The part of our layout_rect the origin of the child is allowed to be in let mut extra_space = Rect::new(0., 0., extra_width, extra_height); if self.in_viewport { - // The part of the viewport the origin of the child is alloed to be in + // The part of the viewport the origin of the child is allowed to be in let viewport = Rect::from_origin_size(self.viewport.origin(), self.viewport.size() - size); // Essentially Rect::intersect but this implementation chooses the point closed to viewport - // inside extra_space to give the child a valid origin + // inside extra_space to give the child a valid origin even if this widget is not inside + // the viewport extra_space.x0 = extra_space.x0.max(viewport.x0).min(extra_space.x1); extra_space.y0 = extra_space.y0.max(viewport.y0).min(extra_space.y1); extra_space.x1 = extra_space.x1.min(viewport.x1).max(extra_space.x0); @@ -121,9 +118,6 @@ impl Align { .resolve(extra_space) .expand(); self.child.set_origin(ctx, data, env, origin); - - let my_insets = self.child.compute_parent_paint_insets(my_size); - ctx.set_paint_insets(my_insets); } } @@ -138,7 +132,7 @@ impl Widget for Align { if let LifeCycle::ViewContextChanged(view_ctx) = event { self.viewport = view_ctx.clip; if self.in_viewport { - self.align(ctx, data, env); + self.align(ctx, data, env, ctx.size()); } } @@ -174,19 +168,22 @@ impl Widget for Align { my_size.height = size.height * height; } - self.my_size = bc.constrain(my_size); - self.align(ctx, data, env); + let my_size = bc.constrain(my_size); + self.align(ctx, data, env, my_size); + + let my_insets = self.child.compute_parent_paint_insets(my_size); + ctx.set_paint_insets(my_insets); if self.height_factor.is_some() { let baseline_offset = self.child.baseline_offset(); if baseline_offset > 0f64 { - ctx.set_baseline_offset(self.my_size.height - self.child.layout_rect().y1 + baseline_offset); + ctx.set_baseline_offset(my_size.height - self.child.layout_rect().y1 + baseline_offset); } } trace!( "Computed layout: origin={}, size={}, insets={:?}", - origin, + self.child.layout_rect().origin(), my_size, my_insets ); From 9aa6ab7f4763fe7d86b88619cd9467549d56b7cf Mon Sep 17 00:00:00 2001 From: xarvic Date: Sun, 11 Dec 2022 18:14:51 +0100 Subject: [PATCH 04/14] update names and docs --- druid/src/widget/viewport_header.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/druid/src/widget/viewport_header.rs b/druid/src/widget/viewport_header.rs index 68cad4eebc..c0d410ac70 100644 --- a/druid/src/widget/viewport_header.rs +++ b/druid/src/widget/viewport_header.rs @@ -59,8 +59,8 @@ impl ViewportHeaderConfig { self.header_side.from_inset(insets).max(0.0).min(max) } - /// The amount of pixels inside the viewport with is overlapped by the header. - pub fn visual_overlapping(&self) -> f64 { + /// The amount of pixels the viewport of the content gets cropped by the header. + pub fn viewport_crop(&self) -> f64 { self.overlapping().min(self.header_size) } @@ -86,12 +86,12 @@ impl ViewportHeaderConfig { pub fn transform_content_scroll_to_view(&self, ctx: &mut EventCtx, rect: Rect) { let axis = self.header_side.axis(); // The length on the major axis with is overlapped by the header. - let overlapping = self.visual_overlapping(); + let viewport_crop = self.viewport_crop(); - if overlapping != 0.0 { + if viewport_crop != 0.0 { ctx.set_handled(); - let new_rect = rect + self.header_side.direction() * overlapping; + let new_rect = rect + self.header_side.direction() * viewport_crop; ctx.submit_notification_without_warning(SCROLL_TO_VIEW.with(new_rect)); } } @@ -141,6 +141,8 @@ impl Widget for ViewportHeader { if let Event::Notification(notification) = event { if let Some(rect) = notification.get(SCROLL_TO_VIEW) { if notification.route() == self.content.id() { + // The content is additionally cropped by the header, therefore we move the scroll + // request by the amount self.header_config.transform_content_scroll_to_view(ctx, *rect); } return; @@ -169,7 +171,7 @@ impl Widget for ViewportHeader { content_view_context.last_mouse_position = None; } content_view_context.clip = content_view_context.clip - - self.header_config.side().as_insets(self.header_config.visual_overlapping()); + self.header_config.side().as_insets(self.header_config.viewport_crop()); self.content.lifecycle(ctx, event, data, env); } From b199525ffd1fee4b93042bf9115b0f15c965d2c5 Mon Sep 17 00:00:00 2001 From: xarvic Date: Sun, 18 Dec 2022 15:24:10 +0100 Subject: [PATCH 05/14] update docs --- druid/src/widget/align.rs | 18 ++++++++++----- druid/src/widget/viewport_header.rs | 35 +++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/druid/src/widget/align.rs b/druid/src/widget/align.rs index 704b42478c..659a5386ed 100644 --- a/druid/src/widget/align.rs +++ b/druid/src/widget/align.rs @@ -86,12 +86,17 @@ impl Align { } } + /// The `Align` widget should only consider the visible space for alignment. + /// + /// When the `Align` widget is fully visible, this option has no effect. When the align widget + /// gets scrolled out of view, the wrapped widget will move to stay inside the visible area. + /// The wrapped widget will always stay inside the bounds of the `Align` widget. fn in_viewport(mut self) -> Self { self.in_viewport = true; self } - fn align<'b, C: CommandCtx<'b>>(&mut self, ctx: &mut C, data: &T, env: &Env, my_size: Size) { + fn align<'b, C: CommandCtx<'b>>(&mut self, ctx: &mut C, my_size: Size) { let size = self.child.layout_rect().size(); let extra_width = (my_size.width - size.width).max(0.); @@ -105,8 +110,8 @@ impl Align { let viewport = Rect::from_origin_size(self.viewport.origin(), self.viewport.size() - size); // Essentially Rect::intersect but this implementation chooses the point closed to viewport - // inside extra_space to give the child a valid origin even if this widget is not inside - // the viewport + // inside extra_space to give the child a valid origin even if extra_space and viewport + // dont intersect. extra_space.x0 = extra_space.x0.max(viewport.x0).min(extra_space.x1); extra_space.y0 = extra_space.y0.max(viewport.y0).min(extra_space.y1); extra_space.x1 = extra_space.x1.min(viewport.x1).max(extra_space.x0); @@ -117,7 +122,7 @@ impl Align { .align .resolve(extra_space) .expand(); - self.child.set_origin(ctx, data, env, origin); + self.child.set_origin(ctx, origin); } } @@ -129,10 +134,11 @@ impl Widget for Align { #[instrument(name = "Align", level = "trace", skip(self, ctx, event, data, env))] fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) { + // THis needs to happen before passing the event to the child. if let LifeCycle::ViewContextChanged(view_ctx) = event { self.viewport = view_ctx.clip; if self.in_viewport { - self.align(ctx, data, env, ctx.size()); + self.align(ctx, ctx.size()); } } @@ -169,7 +175,7 @@ impl Widget for Align { } let my_size = bc.constrain(my_size); - self.align(ctx, data, env, my_size); + self.align(ctx, my_size); let my_insets = self.child.compute_parent_paint_insets(my_size); ctx.set_paint_insets(my_insets); diff --git a/druid/src/widget/viewport_header.rs b/druid/src/widget/viewport_header.rs index c0d410ac70..6c708ab7b8 100644 --- a/druid/src/widget/viewport_header.rs +++ b/druid/src/widget/viewport_header.rs @@ -3,7 +3,10 @@ use crate::{BoxConstraints, Color, Data, Env, Event, EventCtx, Insets, LayoutCtx use crate::commands::SCROLL_TO_VIEW; use crate::widget::flex::{Orientation, Side}; - +/// A widget, containing two widgets with horizontal or vertical layout. +/// +/// When the `ViewportHeader` is moved out of the viewport, the `header` widget tries to stay inside +/// the viewport by moving over the `content` if necessary. pub struct ViewportHeader { header: WidgetPod>>, content: WidgetPod>>, @@ -12,6 +15,7 @@ pub struct ViewportHeader { clip_content: bool, } +/// ViewportHeaderConfig contains the information necessary to create the layout of [`ViewportHeader`] pub struct ViewportHeaderConfig { content_size: Size, viewport: Rect, @@ -21,6 +25,9 @@ pub struct ViewportHeaderConfig { } impl ViewportHeaderConfig { + /// creates a new config. + /// + /// side: the side at which the header is located. pub fn new(side: Side) -> Self { Self { content_size: Size::ZERO, @@ -34,10 +41,12 @@ impl ViewportHeaderConfig { } } + /// The the layout size of header and content together, when both are fully in view. pub fn size(&self) -> Size { self.content_size + Size::from(self.header_side.axis().pack(self.header_size, 0.0)) } + /// The side of the header. pub fn side(&self) -> Side { self.header_side } @@ -83,6 +92,10 @@ impl ViewportHeaderConfig { (content_origin, header_origin) } + /// Updates a `scroll_to_view` request of the content to take the additional viewport crop into + /// account. + /// + /// Dont call call this with requests of the header widget. pub fn transform_content_scroll_to_view(&self, ctx: &mut EventCtx, rect: Rect) { let axis = self.header_side.axis(); // The length on the major axis with is overlapped by the header. @@ -96,19 +109,29 @@ impl ViewportHeaderConfig { } } + /// Updates the ViewContext of the widget. + /// + /// Should be called when the widget receives a `Lifecycle::ViewContextChanged` event. pub fn update_context(&mut self, view_context: ViewContext) { self.viewport = view_context.clip; } + /// Updates the content size. + /// + /// Should be called in layout. pub fn set_content_size(&mut self, content_size: Size) { self.content_size = content_size; } + /// Updates the header size. + /// + /// should be called in layout pub fn set_header_size(&mut self, header_size: Size) { let axis = self.header_side.axis(); self.header_size = axis.major(header_size); } + /// Sets the minimum visible content. pub fn set_minimum_visible_content(&mut self, visible: f64) { self.minimum_visible_content = visible; } @@ -116,6 +139,7 @@ impl ViewportHeaderConfig { } impl ViewportHeader { + /// Creates a new ViewportHeader widget with a given side for the header. pub fn new(content: impl Widget + 'static, header: impl Widget + 'static, side: Side) -> Self { Self { header: WidgetPod::new(Box::new(header)), @@ -125,11 +149,14 @@ impl ViewportHeader { } } + /// The amount of Pixels pub fn with_minimum_visible_content(mut self, minimum_visible_content: f64) -> Self { self.header_config.set_minimum_visible_content(minimum_visible_content); self } + /// Builder-style method to set whether the additional cropped viewport should be clipped from + /// from the content. pub fn clipped_content(mut self, clipped_content: bool) -> Self { self.clip_content = clipped_content; self @@ -163,7 +190,7 @@ impl Widget for ViewportHeader { self.header_config.update_context(*view_context); let (_, header_origin) = self.header_config.origins(); - self.header.set_origin(ctx, data, env, header_origin); + self.header.set_origin(ctx, header_origin); self.header.lifecycle(ctx, event, data, env); let mut content_view_context = *view_context; @@ -206,8 +233,8 @@ impl Widget for ViewportHeader { let (content_origin, header_origin) = self.header_config.origins(); - self.header.set_origin(ctx, data, env, header_origin); - self.content.set_origin(ctx, data, env, content_origin); + self.header.set_origin(ctx, header_origin); + self.content.set_origin(ctx, content_origin); self.header_config.size() } From 6707f203d50d304bc7e66d8fad0c3ca3f1cede2e Mon Sep 17 00:00:00 2001 From: xarvic Date: Sun, 18 Dec 2022 15:25:37 +0100 Subject: [PATCH 06/14] reformat --- druid/examples/viewport_header.rs | 49 +++++++++++++++++---------- druid/src/widget/align.rs | 14 ++++---- druid/src/widget/flex.rs | 16 ++++++--- druid/src/widget/mod.rs | 4 +-- druid/src/widget/viewport_header.rs | 51 ++++++++++++++++++----------- 5 files changed, 84 insertions(+), 50 deletions(-) diff --git a/druid/examples/viewport_header.rs b/druid/examples/viewport_header.rs index e8f8738681..72a35087f2 100755 --- a/druid/examples/viewport_header.rs +++ b/druid/examples/viewport_header.rs @@ -20,8 +20,14 @@ use druid::lens::Unit; use druid::widget::prelude::*; -use druid::widget::{BackgroundBrush, Button, ClipBox, Flex, Label, List, Side, Slider, Tabs, TextBox, ViewportHeader}; -use druid::{AppLauncher, Color, Data, Insets, Lens, LocalizedString, Point, Rect, RoundedRectRadii, Vec2, WidgetExt, WidgetPod, WindowDesc}; +use druid::widget::{ + BackgroundBrush, Button, ClipBox, Flex, Label, List, Side, Slider, Tabs, TextBox, + ViewportHeader, +}; +use druid::{ + AppLauncher, Color, Data, Insets, Lens, LocalizedString, Point, Rect, RoundedRectRadii, Vec2, + WidgetExt, WidgetPod, WindowDesc, +}; use im::Vector; use std::sync::Arc; @@ -47,13 +53,14 @@ pub fn main() { AppLauncher::with_window(window) .log_to_console() - .launch(AppData { list: Vector::new()}) + .launch(AppData { + list: Vector::new(), + }) .expect("launch failed"); } fn build_widget() -> impl Widget { - let list = List::new(||{ - + let list = List::new(|| { let body = Flex::column() .with_child(Label::new("Name:")) .with_default_spacer() @@ -62,9 +69,13 @@ fn build_widget() -> impl Widget { .with_default_spacer() .with_child(Label::new("Info:")) .with_default_spacer() - .with_child(List::new(||TextBox::new().padding(Insets::from(5.0))).lens(Contact::info)) + .with_child(List::new(|| TextBox::new().padding(Insets::from(5.0))).lens(Contact::info)) .with_default_spacer() - .with_child(Button::new("Add Info").on_click(|_, data: &mut Contact, _|data.info.push_back(Arc::new(String::new())))) + .with_child( + Button::new("Add Info").on_click(|_, data: &mut Contact, _| { + data.info.push_back(Arc::new(String::new())) + }), + ) .align_left() .fix_width(150.0) .padding(Insets::uniform_xy(25.0, 0.0)) @@ -73,24 +84,28 @@ fn build_widget() -> impl Widget { ViewportHeader::new( body, - Label::dynamic(|data: &Contact, _|format!("Contact \"{}\"", &data.name)) + Label::dynamic(|data: &Contact, _| format!("Contact \"{}\"", &data.name)) .center() .background(Color::BLACK) .rounded(RoundedRectRadii::new(5.0, 5.0, 0.0, 0.0)), Side::Top, ) - .clipped_content(true) - .with_minimum_visible_content(20.0) - .padding(Insets::uniform_xy(0.0, 5.0)) + .clipped_content(true) + .with_minimum_visible_content(20.0) + .padding(Insets::uniform_xy(0.0, 5.0)) }) - .lens(AppData::list) - .scroll(); + .lens(AppData::list) + .scroll(); Flex::column() .with_flex_child(list, 1.0) .with_default_spacer() - .with_child(Button::new("Add Contact").on_click(|_, data: &mut AppData, _|data.list.push_back(Contact { - name: Arc::new("New Contact".to_string()), - info: Default::default(), - }))) + .with_child( + Button::new("Add Contact").on_click(|_, data: &mut AppData, _| { + data.list.push_back(Contact { + name: Arc::new("New Contact".to_string()), + info: Default::default(), + }) + }), + ) } diff --git a/druid/src/widget/align.rs b/druid/src/widget/align.rs index 659a5386ed..ffdc0cd066 100644 --- a/druid/src/widget/align.rs +++ b/druid/src/widget/align.rs @@ -14,11 +14,11 @@ //! A widget that aligns its child (for example, centering it). +use crate::contexts::CommandCtx; use crate::debug_state::DebugState; use crate::widget::prelude::*; use crate::{Data, Rect, Size, UnitPoint, WidgetPod}; use tracing::{instrument, trace}; -use crate::contexts::CommandCtx; /// A widget that aligns its child. pub struct Align { @@ -107,7 +107,8 @@ impl Align { if self.in_viewport { // The part of the viewport the origin of the child is allowed to be in - let viewport = Rect::from_origin_size(self.viewport.origin(), self.viewport.size() - size); + let viewport = + Rect::from_origin_size(self.viewport.origin(), self.viewport.size() - size); // Essentially Rect::intersect but this implementation chooses the point closed to viewport // inside extra_space to give the child a valid origin even if extra_space and viewport @@ -118,10 +119,7 @@ impl Align { extra_space.y1 = extra_space.y1.min(viewport.y1).max(extra_space.y0); } - let origin = self - .align - .resolve(extra_space) - .expand(); + let origin = self.align.resolve(extra_space).expand(); self.child.set_origin(ctx, origin); } } @@ -183,7 +181,9 @@ impl Widget for Align { if self.height_factor.is_some() { let baseline_offset = self.child.baseline_offset(); if baseline_offset > 0f64 { - ctx.set_baseline_offset(my_size.height - self.child.layout_rect().y1 + baseline_offset); + ctx.set_baseline_offset( + my_size.height - self.child.layout_rect().y1 + baseline_offset, + ); } } diff --git a/druid/src/widget/flex.rs b/druid/src/widget/flex.rs index 2adc0e4512..f4bffcbc89 100644 --- a/druid/src/widget/flex.rs +++ b/druid/src/widget/flex.rs @@ -256,10 +256,18 @@ impl Side { pub fn as_insets(&self, amount: f64) -> Insets { let mut insets = Insets::ZERO; match self { - Side::Top => {insets.y0 = amount;} - Side::Left => {insets.x0 = amount;} - Side::Right => {insets.x1 = amount;} - Side::Bottom => {insets.y1 = amount;} + Side::Top => { + insets.y0 = amount; + } + Side::Left => { + insets.x0 = amount; + } + Side::Right => { + insets.x1 = amount; + } + Side::Bottom => { + insets.y1 = amount; + } } insets } diff --git a/druid/src/widget/mod.rs b/druid/src/widget/mod.rs index 340a5da7a9..590d490599 100644 --- a/druid/src/widget/mod.rs +++ b/druid/src/widget/mod.rs @@ -60,11 +60,11 @@ mod tabs; mod textbox; mod value_textbox; mod view_switcher; +mod viewport_header; #[allow(clippy::module_inception)] mod widget; mod widget_ext; mod z_stack; -mod viewport_header; pub use self::image::Image; pub use added::Added; @@ -80,7 +80,7 @@ pub use controller::{Controller, ControllerHost}; pub use disable_if::DisabledIf; pub use either::Either; pub use env_scope::EnvScope; -pub use flex::{Axis, CrossAxisAlignment, Flex, FlexParams, MainAxisAlignment, Side, Orientation}; +pub use flex::{Axis, CrossAxisAlignment, Flex, FlexParams, MainAxisAlignment, Orientation, Side}; pub use identity_wrapper::IdentityWrapper; pub use intrinsic_width::IntrinsicWidth; pub use label::{Label, LabelText, LineBreaking, RawLabel}; diff --git a/druid/src/widget/viewport_header.rs b/druid/src/widget/viewport_header.rs index 6c708ab7b8..c9be93a032 100644 --- a/druid/src/widget/viewport_header.rs +++ b/druid/src/widget/viewport_header.rs @@ -1,7 +1,10 @@ -use druid::RenderContext; -use crate::{BoxConstraints, Color, Data, Env, Event, EventCtx, Insets, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, Rect, Size, UpdateCtx, ViewContext, Widget, WidgetExt, WidgetId, WidgetPod}; use crate::commands::SCROLL_TO_VIEW; use crate::widget::flex::{Orientation, Side}; +use crate::{ + BoxConstraints, Color, Data, Env, Event, EventCtx, Insets, LayoutCtx, LifeCycle, LifeCycleCtx, + PaintCtx, Point, Rect, Size, UpdateCtx, ViewContext, Widget, WidgetExt, WidgetId, WidgetPod, +}; +use druid::RenderContext; /// A widget, containing two widgets with horizontal or vertical layout. /// @@ -78,15 +81,10 @@ impl ViewportHeaderConfig { let orientation = self.header_side.orientation(); let axis = self.header_side.axis(); - let (first, _) = orientation.order( - axis.major(self.content_size), - self.header_size - ); + let (first, _) = orientation.order(axis.major(self.content_size), self.header_size); - let (content_origin, header_origin) = orientation.order( - Point::ZERO, - Point::from(axis.pack(first, 0.0)) - ); + let (content_origin, header_origin) = + orientation.order(Point::ZERO, Point::from(axis.pack(first, 0.0))); let header_origin = header_origin - self.header_side.direction() * self.overlapping(); (content_origin, header_origin) @@ -135,12 +133,15 @@ impl ViewportHeaderConfig { pub fn set_minimum_visible_content(&mut self, visible: f64) { self.minimum_visible_content = visible; } - } impl ViewportHeader { /// Creates a new ViewportHeader widget with a given side for the header. - pub fn new(content: impl Widget + 'static, header: impl Widget + 'static, side: Side) -> Self { + pub fn new( + content: impl Widget + 'static, + header: impl Widget + 'static, + side: Side, + ) -> Self { Self { header: WidgetPod::new(Box::new(header)), content: WidgetPod::new(Box::new(content)), @@ -151,7 +152,8 @@ impl ViewportHeader { /// The amount of Pixels pub fn with_minimum_visible_content(mut self, minimum_visible_content: f64) -> Self { - self.header_config.set_minimum_visible_content(minimum_visible_content); + self.header_config + .set_minimum_visible_content(minimum_visible_content); self } @@ -170,7 +172,8 @@ impl Widget for ViewportHeader { if notification.route() == self.content.id() { // The content is additionally cropped by the header, therefore we move the scroll // request by the amount - self.header_config.transform_content_scroll_to_view(ctx, *rect); + self.header_config + .transform_content_scroll_to_view(ctx, *rect); } return; } @@ -197,12 +200,17 @@ impl Widget for ViewportHeader { if self.header.is_hot() { content_view_context.last_mouse_position = None; } - content_view_context.clip = content_view_context.clip - - self.header_config.side().as_insets(self.header_config.viewport_crop()); + content_view_context.clip = content_view_context.clip + - self + .header_config + .side() + .as_insets(self.header_config.viewport_crop()); self.content.lifecycle(ctx, event, data, env); } - LifeCycle::BuildFocusChain if self.header_config.side().orientation() == Orientation::End => { + LifeCycle::BuildFocusChain + if self.header_config.side().orientation() == Orientation::End => + { self.content.lifecycle(ctx, event, data, env); self.header.lifecycle(ctx, event, data, env); } @@ -242,12 +250,15 @@ impl Widget for ViewportHeader { fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) { ctx.with_save(|ctx| { if self.clip_content { - let content_rect = self.content.layout_rect() - - self.header_config.side().as_insets(self.header_config.overlapping()); + let content_rect = self.content.layout_rect() + - self + .header_config + .side() + .as_insets(self.header_config.overlapping()); ctx.clip(content_rect); } self.content.paint(ctx, data, env); }); self.header.paint(ctx, data, env); } -} \ No newline at end of file +} From 2549853ae169ce395750a3920010b8d01d3ae5ff Mon Sep 17 00:00:00 2001 From: xarvic Date: Sun, 15 Jan 2023 10:18:05 +0100 Subject: [PATCH 07/14] fix --- druid/examples/z_stack_bug.rs | 32 +++++++++++++++----------------- druid/src/widget/align.rs | 10 +++++----- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/druid/examples/z_stack_bug.rs b/druid/examples/z_stack_bug.rs index cb492261f5..543deb7030 100644 --- a/druid/examples/z_stack_bug.rs +++ b/druid/examples/z_stack_bug.rs @@ -1,5 +1,5 @@ -use druid::{Color, Widget, RenderContext, WindowDesc, AppLauncher, WidgetExt, UnitPoint}; use druid::widget::{CrossAxisAlignment, Flex, Label, Painter, Scroll, SizedBox, ZStack}; +use druid::{AppLauncher, Color, RenderContext, UnitPoint, Widget, WidgetExt, WindowDesc}; fn main() { let window = WindowDesc::new(build_ui()); @@ -11,8 +11,7 @@ fn main() { } fn build_ui() -> impl Widget<()> { - let mut container = Flex::column() - .cross_axis_alignment(CrossAxisAlignment::Fill); + let mut container = Flex::column().cross_axis_alignment(CrossAxisAlignment::Fill); for _ in 0..10 { let stack = ZStack::new( @@ -20,24 +19,23 @@ fn build_ui() -> impl Widget<()> { .align_vertical(UnitPoint::TOP) .expand_width() .fix_height(200.0) - .background(Color::grey8(20)) + .background(Color::grey8(20)), ) - .with_centered_child( - Label::new("Overlay") - .center() - .fix_height(100.0) - .background(Color::grey8(0)) - ); + .with_centered_child( + Label::new("Overlay") + .center() + .fix_height(100.0) + .background(Color::grey8(0)), + ); container.add_child(SizedBox::empty().height(200.0)); - container.add_child(Flex::row() - .with_flex_child(stack) - .with_default_spacer() - .with_child(SizedBox::empty()) - + container.add_child( + Flex::row() + .with_flex_child(stack, 1.0) + .with_default_spacer() + .with_child(SizedBox::empty()), ); } - Scroll::new(container) - .vertical() + Scroll::new(container).vertical() } diff --git a/druid/src/widget/align.rs b/druid/src/widget/align.rs index ffdc0cd066..9a0019acdc 100644 --- a/druid/src/widget/align.rs +++ b/druid/src/widget/align.rs @@ -14,11 +14,11 @@ //! A widget that aligns its child (for example, centering it). -use crate::contexts::CommandCtx; use crate::debug_state::DebugState; use crate::widget::prelude::*; use crate::{Data, Rect, Size, UnitPoint, WidgetPod}; use tracing::{instrument, trace}; +use crate::contexts::ChangeCtx; /// A widget that aligns its child. pub struct Align { @@ -96,7 +96,7 @@ impl Align { self } - fn align<'b, C: CommandCtx<'b>>(&mut self, ctx: &mut C, my_size: Size) { + fn align(&mut self, ctx: &mut impl ChangeCtx, my_size: Size) { let size = self.child.layout_rect().size(); let extra_width = (my_size.width - size.width).max(0.); @@ -110,9 +110,9 @@ impl Align { let viewport = Rect::from_origin_size(self.viewport.origin(), self.viewport.size() - size); - // Essentially Rect::intersect but this implementation chooses the point closed to viewport - // inside extra_space to give the child a valid origin even if extra_space and viewport - // dont intersect. + // Essentially Rect::intersect but if the two rectangles dont intersect this + // implementation chooses the point closed to viewpor inside extra_space to always give + // the child a valid origin. extra_space.x0 = extra_space.x0.max(viewport.x0).min(extra_space.x1); extra_space.y0 = extra_space.y0.max(viewport.y0).min(extra_space.y1); extra_space.x1 = extra_space.x1.min(viewport.x1).max(extra_space.x0); From ec2d89eff543e89165cd75856e3809e6da233d4c Mon Sep 17 00:00:00 2001 From: xarvic Date: Sun, 15 Jan 2023 10:26:52 +0100 Subject: [PATCH 08/14] fix clippy --- druid/examples/z_stack_bug.rs | 4 ++-- druid/src/widget/viewport_header.rs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/druid/examples/z_stack_bug.rs b/druid/examples/z_stack_bug.rs index 543deb7030..29d4dc0996 100644 --- a/druid/examples/z_stack_bug.rs +++ b/druid/examples/z_stack_bug.rs @@ -1,5 +1,5 @@ -use druid::widget::{CrossAxisAlignment, Flex, Label, Painter, Scroll, SizedBox, ZStack}; -use druid::{AppLauncher, Color, RenderContext, UnitPoint, Widget, WidgetExt, WindowDesc}; +use druid::widget::{CrossAxisAlignment, Flex, Label, Scroll, SizedBox, ZStack}; +use druid::{AppLauncher, Color, UnitPoint, Widget, WidgetExt, WindowDesc}; fn main() { let window = WindowDesc::new(build_ui()); diff --git a/druid/src/widget/viewport_header.rs b/druid/src/widget/viewport_header.rs index c9be93a032..1e04fe3789 100644 --- a/druid/src/widget/viewport_header.rs +++ b/druid/src/widget/viewport_header.rs @@ -1,8 +1,8 @@ use crate::commands::SCROLL_TO_VIEW; use crate::widget::flex::{Orientation, Side}; use crate::{ - BoxConstraints, Color, Data, Env, Event, EventCtx, Insets, LayoutCtx, LifeCycle, LifeCycleCtx, - PaintCtx, Point, Rect, Size, UpdateCtx, ViewContext, Widget, WidgetExt, WidgetId, WidgetPod, + BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, + PaintCtx, Point, Rect, Size, UpdateCtx, ViewContext, Widget, WidgetPod, }; use druid::RenderContext; @@ -95,7 +95,6 @@ impl ViewportHeaderConfig { /// /// Dont call call this with requests of the header widget. pub fn transform_content_scroll_to_view(&self, ctx: &mut EventCtx, rect: Rect) { - let axis = self.header_side.axis(); // The length on the major axis with is overlapped by the header. let viewport_crop = self.viewport_crop(); From 68b91dd724a4c5d01e3b8f89f48f1ca2c30d26a7 Mon Sep 17 00:00:00 2001 From: xarvic Date: Sun, 15 Jan 2023 11:05:52 +0100 Subject: [PATCH 09/14] - fix scroll bug like described in https://github.com/linebender/druid/pull/2291 - update example --- druid/examples/viewport_header.rs | 22 ++++++++++------------ druid/src/widget/viewport_header.rs | 12 +++++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/druid/examples/viewport_header.rs b/druid/examples/viewport_header.rs index 72a35087f2..6e6db8dc6a 100755 --- a/druid/examples/viewport_header.rs +++ b/druid/examples/viewport_header.rs @@ -20,10 +20,7 @@ use druid::lens::Unit; use druid::widget::prelude::*; -use druid::widget::{ - BackgroundBrush, Button, ClipBox, Flex, Label, List, Side, Slider, Tabs, TextBox, - ViewportHeader, -}; +use druid::widget::{BackgroundBrush, Button, ClipBox, Flex, Label, List, Padding, Side, Slider, Tabs, TextBox, ViewportHeader}; use druid::{ AppLauncher, Color, Data, Insets, Lens, LocalizedString, Point, Rect, RoundedRectRadii, Vec2, WidgetExt, WidgetPod, WindowDesc, @@ -62,31 +59,31 @@ pub fn main() { fn build_widget() -> impl Widget { let list = List::new(|| { let body = Flex::column() - .with_child(Label::new("Name:")) .with_default_spacer() - .with_child(TextBox::new().lens(Contact::name)) + .with_child(Label::new("Name:").align_left()) .with_default_spacer() + .with_child(TextBox::new().lens(Contact::name).expand_width()) .with_default_spacer() - .with_child(Label::new("Info:")) .with_default_spacer() - .with_child(List::new(|| TextBox::new().padding(Insets::from(5.0))).lens(Contact::info)) + .with_child(Label::new("Info:").align_left()) .with_default_spacer() + .with_child(List::new(|| TextBox::new().padding(Insets::new(15.0, 0.0, 0.0, 10.0)).expand_width()).lens(Contact::info)) .with_child( Button::new("Add Info").on_click(|_, data: &mut Contact, _| { data.info.push_back(Arc::new(String::new())) }), ) + .with_default_spacer() .align_left() - .fix_width(150.0) .padding(Insets::uniform_xy(25.0, 0.0)) - .background(Color::grey8(15)) + .background(Color::grey8(25)) .rounded(RoundedRectRadii::new(0.0, 0.0, 5.0, 5.0)); ViewportHeader::new( body, Label::dynamic(|data: &Contact, _| format!("Contact \"{}\"", &data.name)) .center() - .background(Color::BLACK) + .background(Color::grey8(15)) .rounded(RoundedRectRadii::new(5.0, 5.0, 0.0, 0.0)), Side::Top, ) @@ -95,7 +92,8 @@ fn build_widget() -> impl Widget { .padding(Insets::uniform_xy(0.0, 5.0)) }) .lens(AppData::list) - .scroll(); + .scroll() + .vertical(); Flex::column() .with_flex_child(list, 1.0) diff --git a/druid/src/widget/viewport_header.rs b/druid/src/widget/viewport_header.rs index 1e04fe3789..b788811e92 100644 --- a/druid/src/widget/viewport_header.rs +++ b/druid/src/widget/viewport_header.rs @@ -1,9 +1,6 @@ use crate::commands::SCROLL_TO_VIEW; use crate::widget::flex::{Orientation, Side}; -use crate::{ - BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, - PaintCtx, Point, Rect, Size, UpdateCtx, ViewContext, Widget, WidgetPod, -}; +use crate::{BoxConstraints, Data, Env, Event, EventCtx, InternalEvent, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, Rect, Size, UpdateCtx, ViewContext, Widget, WidgetPod}; use druid::RenderContext; /// A widget, containing two widgets with horizontal or vertical layout. @@ -180,7 +177,12 @@ impl Widget for ViewportHeader { self.header.event(ctx, event, data, env); if self.header.is_hot() && event.is_pointer_event() { - ctx.set_handled(); + if self.content.is_active() { + ctx.set_handled(); + } else { + self.content.event(ctx, &Event::Internal(InternalEvent::MouseLeave), data, env); + return; + } } self.content.event(ctx, event, data, env); } From 6778e858b09379af86e0b6e02a891bdb4a0de14a Mon Sep 17 00:00:00 2001 From: xarvic Date: Sun, 15 Jan 2023 11:32:16 +0100 Subject: [PATCH 10/14] update documentation --- druid/src/widget/flex.rs | 20 ++++++++++++++++++++ druid/src/widget/viewport_header.rs | 12 +++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/druid/src/widget/flex.rs b/druid/src/widget/flex.rs index f4bffcbc89..b072acce37 100644 --- a/druid/src/widget/flex.rs +++ b/druid/src/widget/flex.rs @@ -197,6 +197,9 @@ pub enum Axis { Vertical, } +/// One of two sides of an Axis. +/// +/// This value is useful combination with an axis to indicate a side of a Rectangle. #[derive(Data, Debug, Clone, Copy, PartialEq, Eq)] pub enum Orientation { Start, @@ -204,6 +207,7 @@ pub enum Orientation { } impl Orientation { + /// brings two elements in visual order. pub fn order(&self, reference: T, side: T) -> (T, T) { match self { Orientation::Start => (side, reference), @@ -212,6 +216,7 @@ impl Orientation { } } +/// Represents one of the sides of a Rectangle. #[derive(Data, Debug, Clone, Copy, PartialEq, Eq)] pub enum Side { Top, @@ -221,6 +226,17 @@ pub enum Side { } impl Side { + /// Creates a new Side from an Axis and an Orientation + pub fn pack(axis: Axis, orientation: Orientation) -> Self { + match (axis, orientation) { + (Axis::Horizontal, Orientation::Start) => Side::Left, + (Axis::Horizontal, Orientation::End) => Side::Right, + (Axis::Vertical, Orientation::Start) => Side::Top, + (Axis::Vertical, Orientation::End) => Side::Bottom, + } + } + + /// The Axis of this Side pub fn axis(&self) -> Axis { match self { Side::Top | Side::Bottom => Axis::Vertical, @@ -228,6 +244,7 @@ impl Side { } } + /// Orientation of this Side pub fn orientation(&self) -> Orientation { match self { Side::Top | Side::Left => Orientation::Start, @@ -235,6 +252,7 @@ impl Side { } } + /// returns a Vec2 which points in the direction of this Side. pub fn direction(&self) -> Vec2 { match self { Side::Top => Vec2::new(0.0, -1.0), @@ -244,6 +262,7 @@ impl Side { } } + /// returns the value of an `Inset` at this side. pub fn from_inset(&self, insets: Insets) -> f64 { match self { Side::Top => insets.y0, @@ -253,6 +272,7 @@ impl Side { } } + /// creates an inset which has amount on this side and zero everywhere else. pub fn as_insets(&self, amount: f64) -> Insets { let mut insets = Insets::ZERO; match self { diff --git a/druid/src/widget/viewport_header.rs b/druid/src/widget/viewport_header.rs index b788811e92..adbb0f5696 100644 --- a/druid/src/widget/viewport_header.rs +++ b/druid/src/widget/viewport_header.rs @@ -6,7 +6,8 @@ use druid::RenderContext; /// A widget, containing two widgets with horizontal or vertical layout. /// /// When the `ViewportHeader` is moved out of the viewport, the `header` widget tries to stay inside -/// the viewport by moving over the `content` if necessary. +/// the viewport by moving over the `content` if necessary. It will always stay inside the +/// `ViewportHeader`'s bounds. pub struct ViewportHeader { header: WidgetPod>>, content: WidgetPod>>, @@ -41,6 +42,10 @@ impl ViewportHeaderConfig { } } + pub fn minimum_visible(&self) -> f64 { + self.minimum_visible_content.min(self.header_side.axis().major(self.content_size)) + } + /// The the layout size of header and content together, when both are fully in view. pub fn size(&self) -> Size { self.content_size + Size::from(self.header_side.axis().pack(self.header_size, 0.0)) @@ -63,7 +68,7 @@ impl ViewportHeaderConfig { //Compute max movable distance let axis = self.header_side.axis(); - let max = axis.major(self.content_size) - self.minimum_visible_content; + let max = axis.major(self.content_size) - self.minimum_visible(); self.header_side.from_inset(insets).max(0.0).min(max) } @@ -146,7 +151,8 @@ impl ViewportHeader { } } - /// The amount of Pixels + /// The amount of Pixels on the main axis of the header, which is always visible of the content. + /// pub fn with_minimum_visible_content(mut self, minimum_visible_content: f64) -> Self { self.header_config .set_minimum_visible_content(minimum_visible_content); From 2ddc77fef8e1051d8b54ef8e72fa9a28d144c003 Mon Sep 17 00:00:00 2001 From: xarvic Date: Sun, 15 Jan 2023 12:00:36 +0100 Subject: [PATCH 11/14] improved example --- druid/examples/viewport_header.rs | 54 +++++++++++++++++++++++------ druid/src/widget/viewport_header.rs | 1 - 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/druid/examples/viewport_header.rs b/druid/examples/viewport_header.rs index 6e6db8dc6a..b1386081e0 100755 --- a/druid/examples/viewport_header.rs +++ b/druid/examples/viewport_header.rs @@ -20,23 +20,22 @@ use druid::lens::Unit; use druid::widget::prelude::*; -use druid::widget::{BackgroundBrush, Button, ClipBox, Flex, Label, List, Padding, Side, Slider, Tabs, TextBox, ViewportHeader}; -use druid::{ - AppLauncher, Color, Data, Insets, Lens, LocalizedString, Point, Rect, RoundedRectRadii, Vec2, - WidgetExt, WidgetPod, WindowDesc, -}; +use druid::widget::{BackgroundBrush, Button, ClipBox, Controller, Flex, Label, List, Padding, Side, Slider, Tabs, TextBox, ViewportHeader}; +use druid::{AppLauncher, Color, Data, Insets, Lens, LocalizedString, Point, Rect, RoundedRectRadii, Selector, Vec2, WidgetExt, WidgetPod, WindowDesc}; use im::Vector; use std::sync::Arc; #[derive(Clone, Data, Lens)] struct AppData { list: Vector, + count: usize, } #[derive(Clone, Data, Lens)] struct Contact { name: Arc, info: Vector>, + id: usize, } pub fn main() { @@ -52,6 +51,7 @@ pub fn main() { .log_to_console() .launch(AppData { list: Vector::new(), + count: 0, }) .expect("launch failed"); } @@ -77,14 +77,20 @@ fn build_widget() -> impl Widget { .align_left() .padding(Insets::uniform_xy(25.0, 0.0)) .background(Color::grey8(25)) - .rounded(RoundedRectRadii::new(0.0, 0.0, 5.0, 5.0)); + .rounded(RoundedRectRadii::new(0.0, 0.0, 10.0, 10.0)); + + let header = Flex::row() + .with_flex_child(Label::dynamic(|data: &Contact, _| format!("Contact \"{}\"", &data.name)).center(), 1.0) + .with_child(Button::new("X").on_click(|ctx, data: &mut Contact, _|{ + ctx.submit_notification(REMOVE_ID.with(data.id)) + }).padding(5.0)) + .center() + .background(Color::grey8(15)) + .rounded(RoundedRectRadii::new(10.0, 10.0, 0.0, 0.0)); ViewportHeader::new( body, - Label::dynamic(|data: &Contact, _| format!("Contact \"{}\"", &data.name)) - .center() - .background(Color::grey8(15)) - .rounded(RoundedRectRadii::new(5.0, 5.0, 0.0, 0.0)), + header, Side::Top, ) .clipped_content(true) @@ -92,6 +98,7 @@ fn build_widget() -> impl Widget { .padding(Insets::uniform_xy(0.0, 5.0)) }) .lens(AppData::list) + .controller(RemoveID) .scroll() .vertical(); @@ -100,10 +107,35 @@ fn build_widget() -> impl Widget { .with_default_spacer() .with_child( Button::new("Add Contact").on_click(|_, data: &mut AppData, _| { + let name = if data.count == 0 { + "New Contact".to_string() + } else { + format!("New Contact #{}", data.count) + }; + let id = data.count; + data.count += 1; data.list.push_back(Contact { - name: Arc::new("New Contact".to_string()), + name: Arc::new(name), info: Default::default(), + id, }) }), ) } + +const REMOVE_ID: Selector = Selector::new("org.druid.example.remove_id"); + +struct RemoveID; + +impl> Controller for RemoveID { + fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut AppData, env: &Env) { + if let Event::Notification(notification) = event { + if let Some(id) = notification.get(REMOVE_ID) { + ctx.set_handled(); + data.list.retain(|c|c.id != *id); + } + } else { + child.event(ctx, event, data, env); + } + } +} \ No newline at end of file diff --git a/druid/src/widget/viewport_header.rs b/druid/src/widget/viewport_header.rs index adbb0f5696..ad05c7e4b9 100644 --- a/druid/src/widget/viewport_header.rs +++ b/druid/src/widget/viewport_header.rs @@ -196,7 +196,6 @@ impl Widget for ViewportHeader { fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) { match event { LifeCycle::ViewContextChanged(view_context) => { - println!("update ctx"); self.header_config.update_context(*view_context); let (_, header_origin) = self.header_config.origins(); From 7d0fef1cab2ec923564794c9a37fc2d872872f7c Mon Sep 17 00:00:00 2001 From: xarvic Date: Sun, 15 Jan 2023 12:09:20 +0100 Subject: [PATCH 12/14] reformat --- druid/examples/viewport_header.rs | 59 ++++++++++++++++++++--------- druid/src/widget/align.rs | 2 +- druid/src/widget/viewport_header.rs | 11 ++++-- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/druid/examples/viewport_header.rs b/druid/examples/viewport_header.rs index b1386081e0..1e36b40212 100755 --- a/druid/examples/viewport_header.rs +++ b/druid/examples/viewport_header.rs @@ -20,8 +20,14 @@ use druid::lens::Unit; use druid::widget::prelude::*; -use druid::widget::{BackgroundBrush, Button, ClipBox, Controller, Flex, Label, List, Padding, Side, Slider, Tabs, TextBox, ViewportHeader}; -use druid::{AppLauncher, Color, Data, Insets, Lens, LocalizedString, Point, Rect, RoundedRectRadii, Selector, Vec2, WidgetExt, WidgetPod, WindowDesc}; +use druid::widget::{ + BackgroundBrush, Button, ClipBox, Controller, Flex, Label, List, Padding, Side, Slider, Tabs, + TextBox, ViewportHeader, +}; +use druid::{ + AppLauncher, Color, Data, Insets, Lens, LocalizedString, Point, Rect, RoundedRectRadii, + Selector, Vec2, WidgetExt, WidgetPod, WindowDesc, +}; use im::Vector; use std::sync::Arc; @@ -67,7 +73,14 @@ fn build_widget() -> impl Widget { .with_default_spacer() .with_child(Label::new("Info:").align_left()) .with_default_spacer() - .with_child(List::new(|| TextBox::new().padding(Insets::new(15.0, 0.0, 0.0, 10.0)).expand_width()).lens(Contact::info)) + .with_child( + List::new(|| { + TextBox::new() + .padding(Insets::new(15.0, 0.0, 0.0, 10.0)) + .expand_width() + }) + .lens(Contact::info), + ) .with_child( Button::new("Add Info").on_click(|_, data: &mut Contact, _| { data.info.push_back(Arc::new(String::new())) @@ -80,22 +93,25 @@ fn build_widget() -> impl Widget { .rounded(RoundedRectRadii::new(0.0, 0.0, 10.0, 10.0)); let header = Flex::row() - .with_flex_child(Label::dynamic(|data: &Contact, _| format!("Contact \"{}\"", &data.name)).center(), 1.0) - .with_child(Button::new("X").on_click(|ctx, data: &mut Contact, _|{ - ctx.submit_notification(REMOVE_ID.with(data.id)) - }).padding(5.0)) + .with_flex_child( + Label::dynamic(|data: &Contact, _| format!("Contact \"{}\"", &data.name)).center(), + 1.0, + ) + .with_child( + Button::new("X") + .on_click(|ctx, data: &mut Contact, _| { + ctx.submit_notification(REMOVE_ID.with(data.id)) + }) + .padding(5.0), + ) .center() .background(Color::grey8(15)) .rounded(RoundedRectRadii::new(10.0, 10.0, 0.0, 0.0)); - ViewportHeader::new( - body, - header, - Side::Top, - ) - .clipped_content(true) - .with_minimum_visible_content(20.0) - .padding(Insets::uniform_xy(0.0, 5.0)) + ViewportHeader::new(body, header, Side::Top) + .clipped_content(true) + .with_minimum_visible_content(20.0) + .padding(Insets::uniform_xy(0.0, 5.0)) }) .lens(AppData::list) .controller(RemoveID) @@ -128,14 +144,21 @@ const REMOVE_ID: Selector = Selector::new("org.druid.example.remove_id"); struct RemoveID; impl> Controller for RemoveID { - fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut AppData, env: &Env) { + fn event( + &mut self, + child: &mut W, + ctx: &mut EventCtx, + event: &Event, + data: &mut AppData, + env: &Env, + ) { if let Event::Notification(notification) = event { if let Some(id) = notification.get(REMOVE_ID) { ctx.set_handled(); - data.list.retain(|c|c.id != *id); + data.list.retain(|c| c.id != *id); } } else { child.event(ctx, event, data, env); } } -} \ No newline at end of file +} diff --git a/druid/src/widget/align.rs b/druid/src/widget/align.rs index 9a0019acdc..c48b0c6520 100644 --- a/druid/src/widget/align.rs +++ b/druid/src/widget/align.rs @@ -14,11 +14,11 @@ //! A widget that aligns its child (for example, centering it). +use crate::contexts::ChangeCtx; use crate::debug_state::DebugState; use crate::widget::prelude::*; use crate::{Data, Rect, Size, UnitPoint, WidgetPod}; use tracing::{instrument, trace}; -use crate::contexts::ChangeCtx; /// A widget that aligns its child. pub struct Align { diff --git a/druid/src/widget/viewport_header.rs b/druid/src/widget/viewport_header.rs index ad05c7e4b9..daf356d1ed 100644 --- a/druid/src/widget/viewport_header.rs +++ b/druid/src/widget/viewport_header.rs @@ -1,6 +1,9 @@ use crate::commands::SCROLL_TO_VIEW; use crate::widget::flex::{Orientation, Side}; -use crate::{BoxConstraints, Data, Env, Event, EventCtx, InternalEvent, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Point, Rect, Size, UpdateCtx, ViewContext, Widget, WidgetPod}; +use crate::{ + BoxConstraints, Data, Env, Event, EventCtx, InternalEvent, LayoutCtx, LifeCycle, LifeCycleCtx, + PaintCtx, Point, Rect, Size, UpdateCtx, ViewContext, Widget, WidgetPod, +}; use druid::RenderContext; /// A widget, containing two widgets with horizontal or vertical layout. @@ -43,7 +46,8 @@ impl ViewportHeaderConfig { } pub fn minimum_visible(&self) -> f64 { - self.minimum_visible_content.min(self.header_side.axis().major(self.content_size)) + self.minimum_visible_content + .min(self.header_side.axis().major(self.content_size)) } /// The the layout size of header and content together, when both are fully in view. @@ -186,7 +190,8 @@ impl Widget for ViewportHeader { if self.content.is_active() { ctx.set_handled(); } else { - self.content.event(ctx, &Event::Internal(InternalEvent::MouseLeave), data, env); + self.content + .event(ctx, &Event::Internal(InternalEvent::MouseLeave), data, env); return; } } From a0f7621fcf181c8e7659eb6c641ad0d12dc97861 Mon Sep 17 00:00:00 2001 From: xarvic Date: Sun, 15 Jan 2023 12:21:50 +0100 Subject: [PATCH 13/14] reformat fix clippy suggestions --- druid/examples/viewport_header.rs | 10 +++------- druid/src/widget/align.rs | 10 +++++----- druid/src/widget/flex.rs | 6 ++++++ druid/src/widget/viewport_header.rs | 2 ++ 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/druid/examples/viewport_header.rs b/druid/examples/viewport_header.rs index 1e36b40212..76bcc0da17 100755 --- a/druid/examples/viewport_header.rs +++ b/druid/examples/viewport_header.rs @@ -18,15 +18,11 @@ // On Windows platform, don't show a console when opening the app. #![windows_subsystem = "windows"] -use druid::lens::Unit; use druid::widget::prelude::*; -use druid::widget::{ - BackgroundBrush, Button, ClipBox, Controller, Flex, Label, List, Padding, Side, Slider, Tabs, - TextBox, ViewportHeader, -}; +use druid::widget::{Button, Controller, Flex, Label, List, Side, TextBox, ViewportHeader}; use druid::{ - AppLauncher, Color, Data, Insets, Lens, LocalizedString, Point, Rect, RoundedRectRadii, - Selector, Vec2, WidgetExt, WidgetPod, WindowDesc, + AppLauncher, Color, Data, Insets, Lens, LocalizedString, RoundedRectRadii, Selector, WidgetExt, + WindowDesc, }; use im::Vector; use std::sync::Arc; diff --git a/druid/src/widget/align.rs b/druid/src/widget/align.rs index c48b0c6520..a6f6c2f85c 100644 --- a/druid/src/widget/align.rs +++ b/druid/src/widget/align.rs @@ -91,7 +91,7 @@ impl Align { /// When the `Align` widget is fully visible, this option has no effect. When the align widget /// gets scrolled out of view, the wrapped widget will move to stay inside the visible area. /// The wrapped widget will always stay inside the bounds of the `Align` widget. - fn in_viewport(mut self) -> Self { + pub fn in_viewport(mut self) -> Self { self.in_viewport = true; self } @@ -113,10 +113,10 @@ impl Align { // Essentially Rect::intersect but if the two rectangles dont intersect this // implementation chooses the point closed to viewpor inside extra_space to always give // the child a valid origin. - extra_space.x0 = extra_space.x0.max(viewport.x0).min(extra_space.x1); - extra_space.y0 = extra_space.y0.max(viewport.y0).min(extra_space.y1); - extra_space.x1 = extra_space.x1.min(viewport.x1).max(extra_space.x0); - extra_space.y1 = extra_space.y1.min(viewport.y1).max(extra_space.y0); + extra_space.x0 = extra_space.x0.clamp(viewport.x0, extra_space.x1); + extra_space.y0 = extra_space.y0.clamp(viewport.y0, extra_space.y1); + extra_space.x1 = extra_space.x1.clamp(extra_space.x0, viewport.x1); + extra_space.y1 = extra_space.y1.clamp(extra_space.y0, viewport.y1); } let origin = self.align.resolve(extra_space).expand(); diff --git a/druid/src/widget/flex.rs b/druid/src/widget/flex.rs index b072acce37..e2287e6282 100644 --- a/druid/src/widget/flex.rs +++ b/druid/src/widget/flex.rs @@ -202,7 +202,9 @@ pub enum Axis { /// This value is useful combination with an axis to indicate a side of a Rectangle. #[derive(Data, Debug, Clone, Copy, PartialEq, Eq)] pub enum Orientation { + /// Start Start, + /// End End, } @@ -219,9 +221,13 @@ impl Orientation { /// Represents one of the sides of a Rectangle. #[derive(Data, Debug, Clone, Copy, PartialEq, Eq)] pub enum Side { + /// The top side of a rectangle (y0). Top, + /// The left side of a rectangle (x0). Left, + /// The right side of a rectangle (x1). Right, + /// The bottom side of a rectangle (y1). Bottom, } diff --git a/druid/src/widget/viewport_header.rs b/druid/src/widget/viewport_header.rs index daf356d1ed..3a8ee3a044 100644 --- a/druid/src/widget/viewport_header.rs +++ b/druid/src/widget/viewport_header.rs @@ -45,6 +45,8 @@ impl ViewportHeaderConfig { } } + /// The minimum visible content constrained by the the actual size of the content on that + /// axis. pub fn minimum_visible(&self) -> f64 { self.minimum_visible_content .min(self.header_side.axis().major(self.content_size)) From 9d446b24869c9bf318c12fe43d23c28739fd6b7b Mon Sep 17 00:00:00 2001 From: xarvic Date: Sat, 21 Jan 2023 10:00:27 +0100 Subject: [PATCH 14/14] reformat fix clippy suggestions --- druid/examples/viewport_header.rs | 2 +- druid/src/widget/viewport_header.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/druid/examples/viewport_header.rs b/druid/examples/viewport_header.rs index 76bcc0da17..e73849b2cf 100755 --- a/druid/examples/viewport_header.rs +++ b/druid/examples/viewport_header.rs @@ -18,13 +18,13 @@ // On Windows platform, don't show a console when opening the app. #![windows_subsystem = "windows"] +use druid::im::Vector; use druid::widget::prelude::*; use druid::widget::{Button, Controller, Flex, Label, List, Side, TextBox, ViewportHeader}; use druid::{ AppLauncher, Color, Data, Insets, Lens, LocalizedString, RoundedRectRadii, Selector, WidgetExt, WindowDesc, }; -use im::Vector; use std::sync::Arc; #[derive(Clone, Data, Lens)] diff --git a/druid/src/widget/viewport_header.rs b/druid/src/widget/viewport_header.rs index 3a8ee3a044..78b60fa41b 100644 --- a/druid/src/widget/viewport_header.rs +++ b/druid/src/widget/viewport_header.rs @@ -76,7 +76,7 @@ impl ViewportHeaderConfig { let axis = self.header_side.axis(); let max = axis.major(self.content_size) - self.minimum_visible(); - self.header_side.from_inset(insets).max(0.0).min(max) + self.header_side.from_inset(insets).clamp(0.0, max) } /// The amount of pixels the viewport of the content gets cropped by the header.