Skip to content

Commit

Permalink
Separate paint rects from layout rects
Browse files Browse the repository at this point in the history
This introduces the idea of a 'paint rect', which is the region
that a widget wishes to paint inside. In the general case this is
equivalent to the widget's layout rect; the exceptions are cases
where widgets explicitly wish to paint outside of their bounds.

the paint region is specified as Insets relative to the layout rect.
Widgets that need an explicit paint region specify that region when
handling the Widget::layout method.

closes #401
  • Loading branch information
cmyr committed Feb 6, 2020
1 parent 489aee1 commit a05af99
Show file tree
Hide file tree
Showing 15 changed files with 390 additions and 51 deletions.
44 changes: 40 additions & 4 deletions druid/examples/scroll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use druid::widget::{Button, Flex, Padding, Scroll};
use druid::{AppLauncher, LocalizedString, Widget, WindowDesc};
//! Shows a scroll widget, and also demonstrates how widgets that paint
//! outside their bounds can specify their paint region.
use druid::kurbo::Circle;
use druid::piet::RadialGradient;
use druid::widget::{Flex, Padding, Scroll};
use druid::{
AppLauncher, BoxConstraints, Data, Env, Event, EventCtx, Insets, LayoutCtx, LifeCycle,
LifeCycleCtx, LocalizedString, PaintCtx, Rect, RenderContext, Size, UpdateCtx, Widget,
WindowDesc,
};

fn main() {
let window = WindowDesc::new(build_widget)
Expand All @@ -27,8 +36,35 @@ fn main() {
fn build_widget() -> impl Widget<u32> {
let mut col = Flex::column();
for i in 0..30 {
let button = Button::new(format!("Button {}", i), Button::noop);
col.add_child(Padding::new(3.0, button), 0.0);
col.add_child(Padding::new(3.0, OverPainter(i)), 0.0);
}
Scroll::new(col)
}

/// A widget that paints outside of its bounds.
struct OverPainter(u64);

const INSETS: Insets = Insets::uniform(50.);

impl<T: Data> Widget<T> for OverPainter {
fn event(&mut self, _: &mut EventCtx, _: &Event, _: &mut T, _: &Env) {}

fn lifecycle(&mut self, _: &mut LifeCycleCtx, _: &LifeCycle, _: &T, _: &Env) {}

fn update(&mut self, _: &mut UpdateCtx, _: &T, _: &T, _: &Env) {}

fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, _: &T, _: &Env) -> Size {
ctx.set_paint_insets(INSETS);
bc.constrain(Size::new(100., 100.))
}

fn paint(&mut self, paint_ctx: &mut PaintCtx, _: &T, env: &Env) {
let rect = Rect::ZERO.with_size(paint_ctx.size());
let color = env.get_debug_color(self.0);
let radius = (rect + INSETS).size().height / 2.0;
let circle = Circle::new(rect.center(), radius);
let grad = RadialGradient::new(1.0, (color.clone(), color.clone().with_alpha(0.0)));
paint_ctx.fill(circle, &grad);
paint_ctx.stroke(rect, &color, 2.0);
}
}
19 changes: 18 additions & 1 deletion druid/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use log;
use crate::core::{BaseState, CommandQueue, FocusChange};
use crate::piet::Piet;
use crate::{
Command, Cursor, Rect, Size, Target, Text, TimerToken, WidgetId, WinCtx, WindowHandle, WindowId,
Command, Cursor, Insets, Rect, Size, Target, Text, TimerToken, WidgetId, WinCtx, WindowHandle,
WindowId,
};

/// A mutable context provided to event handling methods of widgets.
Expand Down Expand Up @@ -90,6 +91,7 @@ pub struct UpdateCtx<'a, 'b: 'a> {
/// during widget layout.
pub struct LayoutCtx<'a, 'b: 'a> {
pub(crate) text_factory: &'a mut Text<'b>,
pub(crate) paint_insets: Insets,
pub(crate) window_id: WindowId,
}

Expand Down Expand Up @@ -459,6 +461,21 @@ impl<'a, 'b> LayoutCtx<'a, 'b> {
pub fn window_id(&self) -> WindowId {
self.window_id
}

/// Set explicit paint [`Insets`] for this widget.
///
/// You are not required to set explicit paint bounds unless you need
/// to paint outside of your layout bounds. In this case, the argument
/// should be an [`Insets`] struct that indicates where your widget
/// needs to overpaint, relative to its bounds.
///
/// For more information, see [`WidgetPod::paint_insets`].
///
/// [`Insets`]: struct.Insets.html
/// [`WidgetPod::paint_insets`]: struct.WidgetPod.html#method.paint_insets
pub fn set_paint_insets(&mut self, insets: impl Into<Insets>) {
self.paint_insets = insets.into().nonnegative();
}
}

impl<'a, 'b: 'a> PaintCtx<'a, 'b> {
Expand Down
87 changes: 78 additions & 9 deletions druid/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::collections::VecDeque;
use log;

use crate::bloom::Bloom;
use crate::kurbo::{Affine, Point, Rect, Shape, Size};
use crate::kurbo::{Affine, Insets, Point, Rect, Shape, Size};
use crate::piet::RenderContext;
use crate::{
BoxConstraints, Command, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx,
Expand All @@ -43,7 +43,7 @@ pub(crate) type CommandQueue = VecDeque<(Target, Command)>;
/// needs to propagate, and to provide the previous data so that a
/// widget can process a diff between the old value and the new.
///
/// [`update`]: trait.Widget.html#tymethod.update
/// [`update`]: widget/trait.Widget.html#tymethod.update
pub struct WidgetPod<T: Data, W: Widget<T>> {
state: BaseState,
old_data: Option<T>,
Expand All @@ -64,12 +64,16 @@ pub struct WidgetPod<T: Data, W: Widget<T>> {
/// that, widgets will generally not interact with it directly,
/// but it is an important part of the [`WidgetPod`] struct.
///
/// [`paint`]: trait.Widget.html#tymethod.paint
/// [`paint`]: widget/trait.Widget.html#tymethod.paint
/// [`WidgetPod`]: struct.WidgetPod.html
#[derive(Clone)]
pub(crate) struct BaseState {
pub(crate) id: WidgetId,
pub(crate) layout_rect: Rect,
/// The insets applied to the layout rect to generate the paint rect.
/// In general, these will be zero; the exception is for things like
/// drop shadows or overflowing text.
paint_insets: Insets,

// TODO: consider using bitflags for the booleans.

Expand Down Expand Up @@ -166,13 +170,65 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
self.state.layout_rect = layout_rect;
}

/// Get the layout rectangle.
#[deprecated(since = "0.5.0", note = "use layout_rect() instead")]
#[doc(hidden)]
pub fn get_layout_rect(&self) -> Rect {
self.state.layout_rect
}

/// The layout rectangle.
///
/// This will be same value as set by `set_layout_rect`.
pub fn get_layout_rect(&self) -> Rect {
pub fn layout_rect(&self) -> Rect {
self.state.layout_rect
}

/// Get the widget's paint [`Rect`].
///
/// This is the [`Rect`] that widget has indicated it needs to paint in.
/// This is the same as the [`layout_rect`] with the [`paint_insets`] applied;
/// in the general case it is the same as the [`layout_rect`].
///
/// [`layout_rect`]: #method.layout_rect
/// [`Rect`]: struct.Rect.html
/// [`paint_insets`]: #method.paint_insets
pub fn paint_rect(&self) -> Rect {
self.state.paint_rect()
}

/// Return the paint [`Insets`] for this widget.
///
/// If these [`Insets`] are nonzero, they describe the area beyond a widget's
/// layout rect where it needs to paint.
///
/// These are generally zero; exceptions are widgets that do things like
/// paint a drop shadow.
///
/// A widget can set its insets by calling [`set_paint_insets`] during its
/// [`layout`] method.
///
/// [`Insets`]: struct.Insets.html
/// [`set_paint_insets`]: struct.LayoutCtx.html#method.set_paint_insets
/// [`layout`]: widget/trait.Widget.html#tymethod.layout
pub fn paint_insets(&self) -> Insets {
self.state.paint_insets
}

/// Given a parents layout size, determine the appropriate paint `Insets`
/// for the parent.
///
/// This is a convenience method to be used from the [`layout`] method
/// of a `Widget` that manages a child; it allows the parent to correctly
/// propogate a child's desired paint rect, if it extends beyond the bounds
/// of the parent's layout rect.
///
/// [`layout`]: widget/trait.Widget.html#tymethod.layout
pub fn compute_parent_paint_rect(&self, parent_size: Size) -> Insets {
let parent_bounds = Rect::ZERO.with_size(parent_size);
let union_pant_rect = self.paint_rect().union(parent_bounds);
union_pant_rect - parent_bounds
}

/// Paint a child widget.
///
/// Generally called by container widgets as part of their [`paint`]
Expand All @@ -181,8 +237,8 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
/// Note that this method does not apply the offset of the layout rect.
/// If that is desired, use [`paint_with_offset`] instead.
///
/// [`layout`]: trait.Widget.html#method.layout
/// [`paint`]: trait.Widget.html#method.paint
/// [`layout`]: widget/trait.Widget.html#tymethod.layout
/// [`paint`]: widget/trait.Widget.html#tymethod.paint
/// [`paint_with_offset`]: #method.paint_with_offset
pub fn paint(&mut self, paint_ctx: &mut PaintCtx, data: &T, env: &Env) {
let mut ctx = PaintCtx {
Expand Down Expand Up @@ -225,7 +281,7 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
env: &Env,
paint_if_not_visible: bool,
) {
if !paint_if_not_visible && !paint_ctx.region().intersects(self.state.layout_rect) {
if !paint_if_not_visible && !paint_ctx.region().intersects(self.state.paint_rect()) {
return;
}

Expand Down Expand Up @@ -259,7 +315,10 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
data: &T,
env: &Env,
) -> Size {
self.inner.layout(layout_ctx, bc, data, &env)
layout_ctx.paint_insets = Insets::ZERO;
let size = self.inner.layout(layout_ctx, bc, data, &env);
self.state.paint_insets = layout_ctx.paint_insets;
size
}

/// Propagate an event.
Expand Down Expand Up @@ -526,6 +585,7 @@ impl BaseState {
BaseState {
id,
layout_rect: Rect::ZERO,
paint_insets: Insets::ZERO,
needs_inval: false,
is_hot: false,
is_active: false,
Expand Down Expand Up @@ -554,6 +614,15 @@ impl BaseState {
pub(crate) fn size(&self) -> Size {
self.layout_rect.size()
}

/// The paint region for this widget.
///
/// For more information, see [`WidgetPod::paint_rect`].
///
/// [`WidgetPod::paint_rect`]: struct.WidgetPod.html#method.paint_rect
pub(crate) fn paint_rect(&self) -> Rect {
self.layout_rect + self.paint_insets
}
}

#[cfg(test)]
Expand Down
3 changes: 2 additions & 1 deletion druid/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ impl Env {
}

/// Given an id, returns one of 18 distinct colors
pub(crate) fn get_debug_color(&self, id: u64) -> Color {
#[doc(hidden)]
pub fn get_debug_color(&self, id: u64) -> Color {
let color_num = id as usize % self.0.debug_colors.len();
self.0.debug_colors[color_num].clone()
}
Expand Down
40 changes: 40 additions & 0 deletions druid/src/tests/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,43 @@ impl<T: Data> Widget<T> for ReplaceChild<T> {
self.inner.paint(ctx, data, env)
}
}

// easily make a bunch of WidgetIds
pub fn widget_id2() -> (WidgetId, WidgetId) {
(WidgetId::next(), WidgetId::next())
}

pub fn widget_id3() -> (WidgetId, WidgetId, WidgetId) {
(WidgetId::next(), WidgetId::next(), WidgetId::next())
}

pub fn widget_id4() -> (WidgetId, WidgetId, WidgetId, WidgetId) {
(
WidgetId::next(),
WidgetId::next(),
WidgetId::next(),
WidgetId::next(),
)
}

#[allow(dead_code)]
pub fn widget_id5() -> (WidgetId, WidgetId, WidgetId, WidgetId, WidgetId) {
(
WidgetId::next(),
WidgetId::next(),
WidgetId::next(),
WidgetId::next(),
WidgetId::next(),
)
}

pub fn widget_id6() -> (WidgetId, WidgetId, WidgetId, WidgetId, WidgetId, WidgetId) {
(
WidgetId::next(),
WidgetId::next(),
WidgetId::next(),
WidgetId::next(),
WidgetId::next(),
WidgetId::next(),
)
}
Loading

0 comments on commit a05af99

Please # to comment.