Skip to content

Commit

Permalink
Merge pull request #259 from kas-gui/work2
Browse files Browse the repository at this point in the history
Layout::layout method and layout visitor, make_layout macro
  • Loading branch information
dhardy committed Dec 9, 2021
2 parents 5c73888 + 7f2a9c2 commit 0c6b51f
Show file tree
Hide file tree
Showing 81 changed files with 2,224 additions and 1,858 deletions.
4 changes: 2 additions & 2 deletions crates/kas-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ path = "../kas-macros"
[dependencies.kas-text]
# version = "0.4.0"
git = "https://github.com/kas-gui/kas-text.git"
rev = "60db64b"
rev = "818515e"

[dependencies.winit]
# Provides translations for several winit types
version = "0.25"
version = "0.26"
optional = true
features = ["serde"]
35 changes: 15 additions & 20 deletions crates/kas-core/src/core/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use super::Layout;
use super::Widget;
use crate::event::{self, Manager};
use crate::geom::Rect;
use crate::{dir::Direction, layout, WindowId};
use crate::layout::StorageChain;
use crate::{dir::Direction, WindowId};

#[cfg(feature = "winit")]
pub use winit::window::Icon;
Expand Down Expand Up @@ -115,31 +116,25 @@ fn size_of_option_widget_id() {
/// Common widget data
///
/// All widgets should embed a `#[widget_core] core: CoreData` field.
#[derive(Clone, Default, Debug)]
#[derive(Default, Debug)]
pub struct CoreData {
pub layout: StorageChain,
pub rect: Rect,
pub id: WidgetId,
pub disabled: bool,
}

/// Trait to describe the type needed by the layout implementation.
///
/// The (non-trivial) [`layout`] engines require a storage field within their
/// widget. For manual [`Layout`] implementations this may be specified
/// directly, but to allow the `derive(Widget)` macro to specify the appropriate
/// data type, a widget should include a field of the following form:
/// ```none
/// #[layout_data] layout_data: <Self as kas::LayoutData>::Data,
/// ```
///
/// Ideally we would use an inherent associated type on the struct in question,
/// but until rust-lang#8995 is implemented that is not possible. We also cannot
/// place this associated type on the [`Widget`] trait itself, since then uses
/// of the trait would require parameterisation. Thus, this trait.
pub trait LayoutData {
type Data: Clone + fmt::Debug + Default;
type Solver: layout::RulesSolver;
type Setter: layout::RulesSetter;
/// Note: the clone has default-initialised layout storage and identifier.
/// Configuration and layout solving is required as for any other widget.
impl Clone for CoreData {
fn clone(&self) -> Self {
CoreData {
layout: StorageChain::default(),
rect: self.rect,
id: WidgetId::default(),
disabled: self.disabled,
}
}
}

/// A widget which escapes its parent's rect
Expand Down
17 changes: 5 additions & 12 deletions crates/kas-core/src/core/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::any::Any;

use super::*;
use crate::draw::{DrawHandle, SizeHandle};
use crate::event::{self, Event, Manager, Response};
use crate::event::{self, Event, Manager, ManagerState, Response};
use crate::geom::{Coord, Rect};
use crate::layout::{AlignHints, AxisInfo, SizeRules};
use crate::{CoreData, WidgetId};
Expand Down Expand Up @@ -67,13 +67,6 @@ impl<M: 'static> WidgetChildren for Box<dyn Widget<Msg = M>> {
fn find_leaf_mut(&mut self, id: WidgetId) -> Option<&mut dyn WidgetConfig> {
self.as_mut().find_leaf_mut(id)
}

fn walk_children_dyn(&self, f: &mut dyn FnMut(&dyn WidgetConfig)) {
self.as_ref().walk_children_dyn(f);
}
fn walk_children_mut_dyn(&mut self, f: &mut dyn FnMut(&mut dyn WidgetConfig)) {
self.as_mut().walk_children_mut_dyn(f);
}
}

impl<M: 'static> WidgetConfig for Box<dyn Widget<Msg = M>> {
Expand Down Expand Up @@ -101,12 +94,12 @@ impl<M: 'static> Layout for Box<dyn Widget<Msg = M>> {
self.as_mut().set_rect(mgr, rect, align);
}

fn find_id(&self, coord: Coord) -> Option<WidgetId> {
self.as_ref().find_id(coord)
fn find_id(&mut self, coord: Coord) -> Option<WidgetId> {
self.as_mut().find_id(coord)
}

fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) {
self.as_ref().draw(draw_handle, mgr, disabled);
fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) {
self.as_mut().draw(draw, mgr, disabled);
}
}

Expand Down
118 changes: 47 additions & 71 deletions crates/kas-core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::fmt;
use crate::draw::{DrawHandle, InputState, SizeHandle};
use crate::event::{self, ConfigureManager, Manager, ManagerState};
use crate::geom::{Coord, Offset, Rect};
use crate::layout::{AlignHints, AxisInfo, SizeRules};
use crate::layout::{self, AlignHints, AxisInfo, SizeRules};
use crate::{CoreData, TkAction, WidgetId};

impl dyn WidgetCore {
Expand Down Expand Up @@ -266,48 +266,6 @@ pub trait WidgetChildren: WidgetCore {
None
}
}

/// Walk through all widgets, calling `f` once on each.
///
/// This walk is iterative (nonconcurrent), depth-first, and always calls
/// `f` on self *after* walking through all children.
fn walk_children<F: FnMut(&dyn WidgetConfig)>(&self, mut f: F)
where
Self: Sized,
{
self.walk_children_dyn(&mut f)
}

#[doc(hidden)]
fn walk_children_dyn(&self, f: &mut dyn FnMut(&dyn WidgetConfig)) {
for i in 0..self.num_children() {
if let Some(w) = self.get_child(i) {
w.walk_children_dyn(f);
}
}
f(self.as_widget());
}

/// Walk through all widgets, calling `f` once on each.
///
/// This walk is iterative (nonconcurrent), depth-first, and always calls
/// `f` on self *after* walking through all children.
fn walk_children_mut<F: FnMut(&mut dyn WidgetConfig)>(&mut self, mut f: F)
where
Self: Sized,
{
self.walk_children_mut_dyn(&mut f)
}

#[doc(hidden)]
fn walk_children_mut_dyn(&mut self, f: &mut dyn FnMut(&mut dyn WidgetConfig)) {
for i in 0..self.num_children() {
if let Some(w) = self.get_child_mut(i) {
w.walk_children_mut_dyn(f);
}
}
f(self.as_widget_mut());
}
}

/// Widget configuration
Expand Down Expand Up @@ -403,6 +361,14 @@ pub trait WidgetConfig: Layout {
///
/// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro
pub trait Layout: WidgetChildren {
/// Make a layout
///
/// If used, this allows automatic implementation of `size_rules` and
/// `set_rect` methods. The default case is the empty layout.
fn layout(&mut self) -> layout::Layout<'_> {
Default::default() // TODO: remove default impl
}

/// Get size rules for the given axis
///
/// This method takes `&mut self` to allow local caching of child widget
Expand All @@ -416,7 +382,9 @@ pub trait Layout: WidgetChildren {
///
/// For widgets with children, a [`crate::layout::RulesSolver`] engine may be
/// useful to calculate requirements of complex layouts.
fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules;
fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules {
self.layout().size_rules(size_handle, axis)
}

/// Apply a given `rect` to self
///
Expand All @@ -435,23 +403,22 @@ pub trait Layout: WidgetChildren {
/// One may assume that `size_rules` has been called at least once for each
/// axis with current size information before this method, however
/// `size_rules` might not be re-called before calling `set_rect` again.
#[inline]
fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) {
let _ = (mgr, align);
self.core_data_mut().rect = rect;
self.layout().set_rect(mgr, rect, align);
}

/// Get translation of a child
/// Get translation of children
///
/// Children may live in a translated coordinate space relative to their
/// parent. This method returns an offset which should be *added* to a
/// coordinate to translate *into* the child's coordinate space or
/// subtracted to translate out.
///
/// In most cases, the translation will be zero. Widgets should return
/// [`Offset::ZERO`] for non-existant children.
/// Note that this should only be non-zero for widgets themselves
/// implementing scrolling (and thin wrappers around these).
#[inline]
fn translation(&self, _child_index: usize) -> Offset {
fn translation(&self) -> Offset {
Offset::ZERO
}

Expand All @@ -478,6 +445,8 @@ pub trait Layout: WidgetChildren {
return None;
}

let reverse = reverse ^ self.layout().is_reversed();

if let Some(index) = from {
match reverse {
false if index < last => Some(index + 1),
Expand All @@ -494,29 +463,31 @@ pub trait Layout: WidgetChildren {

/// Find a widget by coordinate
///
/// Used to find the widget responsible for handling events at this `coord`
/// — usually the leaf-most widget containing the coordinate.
///
/// The default implementation suffices for widgets without children;
/// otherwise this is usually implemented as follows:
///
/// 1. return `None` if `!self.rect().contains(coord)`
/// 2. for each `child`, check whether `child.find_id(coord)` returns
/// `Some(id)`, and if so return this result (parents with many children
/// might use a faster search strategy here)
/// 3. otherwise, return `Some(self.id())`
///
/// Exceptionally, a widget may deviate from this behaviour, but only when
/// the coord is within the widget's own rect (example: `CheckBox` contains
/// an embedded `CheckBoxBare` and always forwards this child's id).
///
/// This must not be called before [`Layout::set_rect`].
#[inline]
fn find_id(&self, coord: Coord) -> Option<WidgetId> {
/// This method has a default implementation, but this may need to be
/// overridden if any of the following are true:
///
/// - [`Self::layout`] is not implemented and there are child widgets
/// - Any child widget should not receive events despite having a
/// placement in the layout — e.g. push-buttons (but note that
/// [`crate::layout::Layout::button`] does take this into account)
/// - Mouse/touch events should be passed to a child widget *outside* of
/// this child's area — e.g. a label next to a checkbox
/// - The child widget is in a translated coordinate space *not equal* to
/// [`Self::translation`]
///
/// This method translates from coordinates (usually from mouse input) to a
/// [`WidgetId`]. It is expected to do the following:
///
/// - Return `None` if `coord` is not within `self.rect()`
/// - Find the child which should respond to input at `coord`, if any, and
/// call `find_id` recursively on this child
/// - Otherwise return `self.id()`
fn find_id(&mut self, coord: Coord) -> Option<WidgetId> {
if !self.rect().contains(coord) {
return None;
}
Some(self.id())
let coord = coord + self.translation();
self.layout().find_id(coord).or_else(|| Some(self.id()))
}

/// Draw a widget and its children
Expand All @@ -530,7 +501,12 @@ pub trait Layout: WidgetChildren {
///
/// [`WidgetCore::input_state`] may be used to obtain an [`InputState`] to
/// determine active visual effects.
fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool);
///
/// The default impl draws all children. TODO: have default?
fn draw(&mut self, draw: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) {
let state = self.input_state(mgr, disabled);
self.layout().draw(draw, mgr, state);
}
}

/// Widget trait
Expand Down
4 changes: 2 additions & 2 deletions crates/kas-core/src/draw/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ use std::any::Any;
/// # _pd: std::marker::PhantomData<DS>,
/// # }
/// impl CircleWidget {
/// fn draw(&self, draw_handle: &mut dyn DrawHandle) {
/// fn draw(&mut self, draw: &mut dyn DrawHandle) {
/// // This type assumes usage of kas_wgpu without a custom draw pipe:
/// type DrawIface = DrawIface<kas_wgpu::draw::DrawPipe<()>>;
/// if let Some(mut draw) = DrawIface::downcast_from(draw_handle.draw_device()) {
/// if let Some(mut draw) = DrawIface::downcast_from(draw.draw_device()) {
/// draw.circle(self.rect.into(), 0.9, Rgba::BLACK);
/// }
/// }
Expand Down
6 changes: 3 additions & 3 deletions crates/kas-core/src/draw/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -750,15 +750,15 @@ where
mod test {
use super::*;

fn _draw_handle_ext(draw_handle: &mut dyn DrawHandle) {
fn _draw_handle_ext(draw: &mut dyn DrawHandle) {
// We can't call this method without constructing an actual DrawHandle.
// But we don't need to: we just want to test that methods are callable.

let _scale = draw_handle.size_handle().scale_factor();
let _scale = draw.size_handle().scale_factor();

let text = crate::text::Text::new_single("sample");
let class = TextClass::Label;
let state = InputState::empty();
draw_handle.text_selected(Coord::ZERO, &text, .., class, state)
draw.text_selected(Coord::ZERO, &text, .., class, state)
}
}
20 changes: 17 additions & 3 deletions crates/kas-core/src/layout/align.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub use crate::text::Align;
/// # let rect = Rect::new(Coord::ZERO, Size::ZERO);
/// let pref_size = Size(30, 20); // usually size comes from SizeHandle
/// let rect = align
/// .complete(Align::Stretch, Align::Centre)
/// .complete(Align::Stretch, Align::Center)
/// .aligned_rect(pref_size, rect);
/// // self.core.rect = rect;
/// ```
Expand All @@ -39,11 +39,25 @@ impl AlignHints {
/// No hints
pub const NONE: AlignHints = AlignHints::new(None, None);

/// Center on both axes
pub const CENTER: AlignHints = AlignHints::new(Some(Align::Center), Some(Align::Center));

/// Stretch on both axes
pub const STRETCH: AlignHints = AlignHints::new(Some(Align::Stretch), Some(Align::Stretch));

/// Construct with optional horiz. and vert. alignment
pub const fn new(horiz: Option<Align>, vert: Option<Align>) -> Self {
Self { horiz, vert }
}

/// Combine two hints (first takes priority)
pub fn combine(self, rhs: AlignHints) -> Self {
Self {
horiz: self.horiz.or(rhs.horiz),
vert: self.vert.or(rhs.vert),
}
}

/// Unwrap type's alignments or substitute parameters
pub fn unwrap_or(self, horiz: Align, vert: Align) -> (Align, Align) {
(self.horiz.unwrap_or(horiz), self.vert.unwrap_or(vert))
Expand Down Expand Up @@ -74,15 +88,15 @@ impl CompleteAlignment {
let mut size = rect.size;
if ideal.0 < size.0 && self.halign != Align::Stretch {
pos.0 += match self.halign {
Align::Centre => (size.0 - ideal.0) / 2,
Align::Center => (size.0 - ideal.0) / 2,
Align::BR => size.0 - ideal.0,
Align::Default | Align::TL | Align::Stretch => 0,
};
size.0 = ideal.0;
}
if ideal.1 < size.1 && self.valign != Align::Stretch {
pos.1 += match self.valign {
Align::Centre => (size.1 - ideal.1) / 2,
Align::Center => (size.1 - ideal.1) / 2,
Align::BR => size.1 - ideal.1,
Align::Default | Align::TL | Align::Stretch => 0,
};
Expand Down
Loading

0 comments on commit 0c6b51f

Please sign in to comment.