Skip to content

Commit

Permalink
Merge pull request #305 from kas-gui/work2
Browse files Browse the repository at this point in the history
New Component trait; make Layout::size_rules optional; better layout for menus
  • Loading branch information
dhardy committed Apr 15, 2022
2 parents 58acfd4 + b59545f commit 905e877
Show file tree
Hide file tree
Showing 37 changed files with 1,060 additions and 482 deletions.
175 changes: 175 additions & 0 deletions crates/kas-core/src/component.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// 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 in the LICENSE-APACHE file or at:
// https://www.apache.org/licenses/LICENSE-2.0

//! Widget components

use crate::geom::{Coord, Rect, Size};
use crate::layout::{Align, AlignHints, AxisInfo, SetRectMgr, SizeRules};
use crate::text::{format, AccelString, Text, TextApi};
use crate::theme::{DrawMgr, IdCoord, IdRect, MarkStyle, SizeMgr, TextClass};
use crate::{TkAction, WidgetId};
use kas_macros::{autoimpl, impl_scope};

/// Components are not true widgets, but support layout solving
///
/// TODO: since this is a sub-set of widget functionality, should [`crate::Widget`]
/// extend `Component`? (Significant trait revision would be required.)
pub trait Component {
/// Get size rules for the given axis
fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules;

/// Apply a given `rect` to self
fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints);

/// True if the layout direction is up/left (reverse reading direction)
///
/// TODO: replace with spatial_nav?
fn is_reversed(&self) -> bool {
false
}

/// Translate a coordinate to a [`WidgetId`]
fn find_id(&mut self, coord: Coord) -> Option<WidgetId>;

/// Draw the component and its children
fn draw(&mut self, draw: DrawMgr, id: &WidgetId);
}

impl_scope! {
/// A label component
#[impl_default(where T: trait)]
#[autoimpl(Clone, Debug where T: trait)]
pub struct Label<T: format::FormattableText> {
text: Text<T>,
class: TextClass = TextClass::Label(false),
pos: Coord,
}

impl Self {
/// Construct
#[inline]
pub fn new(label: T, class: TextClass) -> Self {
Label {
text: Text::new_single(label),
class,
pos: Default::default(),
}
}

/// Get text
pub fn as_str(&self) -> &str {
self.text.as_str()
}

/// Set the text and prepare
///
/// Update text and trigger a resize if necessary.
///
/// The `avail` parameter is used to determine when a resize is required. If
/// this parameter is a little bit wrong then resizes may sometimes happen
/// unnecessarily or may not happen when text is slightly too big (e.g.
/// spills into the margin area); this behaviour is probably acceptable.
/// Passing `Size::ZERO` will always resize (unless text is empty).
/// Passing `Size::MAX` should never resize.
pub fn set_text_and_prepare(&mut self, s: T, avail: Size) -> TkAction {
self.text.set_text(s);
crate::text::util::prepare_if_needed(&mut self.text, avail)
}

/// Set the text from a string and prepare
///
/// Update text and trigger a resize if necessary.
///
/// The `avail` parameter is used to determine when a resize is required. If
/// this parameter is a little bit wrong then resizes may sometimes happen
/// unnecessarily or may not happen when text is slightly too big (e.g.
/// spills into the margin area); this behaviour is probably acceptable.
pub fn set_string_and_prepare(&mut self, s: String, avail: Size) -> TkAction
where
T: format::EditableText,
{
use crate::text::EditableTextApi;
self.text.set_string(s);
crate::text::util::prepare_if_needed(&mut self.text, avail)
}

/// Get class
pub fn class(&self) -> TextClass {
self.class
}

/// Set class
///
/// This may influence layout.
pub fn set_class(&mut self, class: TextClass) -> TkAction {
self.class = class;
TkAction::RESIZE
}
}

impl Label<AccelString> {
/// Get the accelerator keys
pub fn keys(&self) -> &[crate::event::VirtualKeyCode] {
self.text.text().keys()
}
}

impl Component for Self {
fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules {
mgr.text_bound(&mut self.text, self.class, axis)
}

fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints) {
self.pos = rect.pos;
let halign = match self.class {
TextClass::Button => Align::Center,
_ => Align::Default,
};
let align = align.unwrap_or(halign, Align::Center);
mgr.text_set_size(&mut self.text, self.class, rect.size, align);
}

fn find_id(&mut self, _: Coord) -> Option<WidgetId> {
None
}

fn draw(&mut self, mut draw: DrawMgr, id: &WidgetId) {
draw.text_effects(IdCoord(id, self.pos), &self.text, self.class);
}
}
}

impl_scope! {
/// A mark
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Mark {
pub style: MarkStyle,
pub rect: Rect,
}
impl Self {
/// Construct
pub fn new(style: MarkStyle) -> Self {
let rect = Rect::ZERO;
Mark { style, rect }
}
}
impl Component for Self {
fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules {
mgr.mark(self.style, axis)
}

fn set_rect(&mut self, _: &mut SetRectMgr, rect: Rect, _: AlignHints) {
self.rect = rect;
}

fn find_id(&mut self, _: Coord) -> Option<WidgetId> {
None
}

fn draw(&mut self, mut draw: DrawMgr, id: &WidgetId) {
draw.mark(IdRect(id, self.rect), self.style);
}
}
}
59 changes: 34 additions & 25 deletions crates/kas-core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ pub trait WidgetChildren: WidgetCore {
/// Widgets are *configured* on window creation or dynamically via the
/// parent calling [`SetRectMgr::configure`]. Parent widgets are responsible
/// for ensuring that children are configured before calling
/// [`Layout::size_rules`]. Configuration may be repeated and may be used as a
/// mechanism to change a child's [`WidgetId`], but this may be expensive.
/// [`Layout::size_rules`] or [`Layout::set_rect`]. Configuration may be
/// repeated and may be used as a mechanism to change a child's [`WidgetId`],
/// but this may be expensive.
///
/// Configuration invokes [`Self::configure_recurse`] which then calls
/// [`Self::configure`]. The latter may be used to load assets before sizing.
Expand Down Expand Up @@ -229,10 +230,13 @@ pub trait WidgetConfig: Layout {
/// methods may be required (e.g. [`Self::set_rect`] to position child
/// elements).
///
/// Layout solving happens in two steps:
/// Two methods of setting layout are possible:
///
/// 1. [`Self::size_rules`] calculates size requirements recursively
/// 2. [`Self::set_rect`] applies the result recursively
/// 1. Use [`layout::solve_size_rules`] or [`layout::SolveCache`] to solve and
/// set layout. This functions by calling [`Self::size_rules`] for each
/// axis then calling [`Self::set_rect`].
/// 2. Only call [`Self::set_rect`]. For some widgets this is fine but for
/// others the internal layout will be incorrect.
///
/// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro
#[autoimpl(for<T: trait + ?Sized> Box<T>)]
Expand All @@ -258,12 +262,11 @@ pub trait Layout: WidgetChildren {
///
/// Typically, this method is called twice: first for the horizontal axis,
/// second for the vertical axis (with resolved width available through
/// the `axis` parameter allowing content wrapping). On re-sizing, the
/// first or both method calls may be skipped.
/// the `axis` parameter allowing content wrapping).
///
/// This method takes `&mut self` since it may be necessary to store child
/// element size rules in order to calculate layout by `size_rules` on the
/// second axis and by `set_rect`.
/// When called, this method should cache any data required to determine
/// internal layout (of child widgets and other components), especially data
/// which requires calling `size_rules` on children.
///
/// This method may be implemented through [`Self::layout`] or directly.
/// A [`crate::layout::RulesSolver`] engine may be useful to calculate
Expand All @@ -275,25 +278,25 @@ pub trait Layout: WidgetChildren {
self.layout().size_rules(size_mgr, axis)
}

/// Apply a given `rect` to self
/// Set size and position
///
/// This method applies the layout resolved by [`Self::size_rules`].
/// This is the final step to layout solving. It may be influenced by
/// [`Self::size_rules`], but it is not guaranteed that `size_rules` is
/// called first. After calling `set_rect`, the widget must be ready for
/// calls to [`Self::draw`] and event handling.
///
/// This method may be implemented through [`Self::layout`] or directly.
/// For widgets without children, typically this method only stores the
/// calculated `rect`, which is done by the default implementation (even
/// with the default empty layout for [`Self::layout`]).
///
/// This method may also be useful for alignment, which may be applied in
/// one of two ways:
/// The size of the assigned `rect` is normally at least the minimum size
/// requested by [`Self::size_rules`], but this is not guaranteed. In case
/// this minimum is not met, it is permissible for the widget to draw
/// outside of its assigned `rect` and to not function as normal.
///
/// 1. Shrinking `rect` to the "ideal size" and aligning within (see
/// [`crate::layout::CompleteAlignment::aligned_rect`] or example usage in
/// `CheckBoxBare` widget)
/// 2. Applying alignment to contents (see for example `Label` widget)
/// The assigned `rect` may be larger than the widget's size requirements.
/// It is up to the widget to either stretch to occupy this space or align
/// itself within the excess space, according to the `align` hints provided.
///
/// One may assume that `size_rules` has been called at least once for each
/// axis with current size information before this method.
/// This method may be implemented through [`Self::layout`] or directly.
/// The default implementation assigns `self.core_data_mut().rect = rect`
/// and applies the layout described by [`Self::layout`].
fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints) {
self.core_data_mut().rect = rect;
self.layout().set_rect(mgr, rect, align);
Expand Down Expand Up @@ -360,6 +363,9 @@ pub trait Layout: WidgetChildren {
/// inner content, while the `CheckBox` widget forwards click events to its
/// `CheckBoxBare` component.
///
/// It is expected that [`Self::set_rect`] is called before this method,
/// but failure to do so should not cause a fatal error.
///
/// The default implementation suffices unless:
///
/// - [`Self::layout`] is not implemented and there are child widgets
Expand Down Expand Up @@ -387,6 +393,9 @@ pub trait Layout: WidgetChildren {
/// This method is invoked each frame to draw visible widgets. It should
/// draw itself and recurse into all visible children.
///
/// It is expected that [`Self::set_rect`] is called before this method,
/// but failure to do so should not cause a fatal error.
///
/// The default impl draws elements as defined by [`Self::layout`].
fn draw(&mut self, draw: DrawMgr) {
let id = self.id(); // clone to avoid borrow conflict
Expand Down
27 changes: 13 additions & 14 deletions crates/kas-core/src/layout/grid_solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ use super::{GridStorage, RowTemp, RulesSetter, RulesSolver};
use crate::cast::{Cast, Conv};
use crate::geom::{Coord, Offset, Rect, Size};

/// Bound on [`GridSolver`] type parameters
pub trait DefaultWithLen {
// Construct with default elements of given length; panic on failure
/// Construct with default elements of given length; panic on failure
fn default_with_len(len: usize) -> Self;
}
impl<T: Copy + Default, const N: usize> DefaultWithLen for [T; N] {
Expand Down Expand Up @@ -105,11 +106,11 @@ impl<CSR: DefaultWithLen, RSR: DefaultWithLen, S: GridStorage> GridSolver<CSR, R
fn prepare(&mut self, storage: &mut S) {
if self.axis.has_fixed {
if self.axis.is_vertical() {
let (widths, rules, total) = storage.widths_rules_total();
SizeRules::solve_seq_total(widths, rules, total, self.axis.other_axis);
let (widths, rules) = storage.widths_and_rules();
SizeRules::solve_seq(widths, rules, self.axis.other_axis);
} else {
let (heights, rules, total) = storage.heights_rules_total();
SizeRules::solve_seq_total(heights, rules, total, self.axis.other_axis);
let (heights, rules) = storage.heights_and_rules();
SizeRules::solve_seq(heights, rules, self.axis.other_axis);
}
}

Expand Down Expand Up @@ -243,18 +244,14 @@ where
rules.distribute_span_over(&mut widths[begin..end]);
}

widths.iter().sum()
SizeRules::sum(widths)
}

let rules;
if self.axis.is_horizontal() {
rules = calculate(storage.width_rules(), self.col_spans.as_mut());
storage.set_width_total(rules);
calculate(storage.width_rules(), self.col_spans.as_mut())
} else {
rules = calculate(storage.height_rules(), self.row_spans.as_mut());
storage.set_height_total(rules);
calculate(storage.height_rules(), self.row_spans.as_mut())
}
rules
}
}

Expand Down Expand Up @@ -286,7 +283,8 @@ impl<CT: RowTemp, RT: RowTemp, S: GridStorage> GridSetter<CT, RT, S> {

if cols > 0 {
let align = align.horiz.unwrap_or(Align::Default);
let (widths, rules, total) = storage.widths_rules_total();
let (widths, rules) = storage.widths_and_rules();
let total = SizeRules::sum(rules);
let max_size = total.max_size();
let mut target = rect.size.0;

Expand All @@ -312,7 +310,8 @@ impl<CT: RowTemp, RT: RowTemp, S: GridStorage> GridSetter<CT, RT, S> {

if rows > 0 {
let align = align.vert.unwrap_or(Align::Default);
let (heights, rules, total) = storage.heights_rules_total();
let (heights, rules) = storage.heights_and_rules();
let total = SizeRules::sum(rules);
let max_size = total.max_size();
let mut target = rect.size.1;

Expand Down
20 changes: 18 additions & 2 deletions crates/kas-core/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ mod visitor;
use crate::dir::{Direction, Directional};
use crate::draw::DrawShared;
use crate::event::EventState;
use crate::theme::{SizeHandle, SizeMgr};
use crate::geom::{Size, Vec2};
use crate::text::TextApi;
use crate::theme::{SizeHandle, SizeMgr, TextClass};
use crate::{TkAction, WidgetConfig, WidgetId};
use std::ops::{Deref, DerefMut};

Expand All @@ -60,7 +62,7 @@ pub use size_rules::SizeRules;
pub use size_types::*;
pub use sizer::{solve_size_rules, RulesSetter, RulesSolver, SolveCache};
pub use storage::*;
pub use visitor::{FrameStorage, Layout, StorageChain, TextStorage};
pub use visitor::{FrameStorage, Layout, StorageChain};

/// Information on which axis is being resized
///
Expand Down Expand Up @@ -197,6 +199,20 @@ impl<'a> SetRectMgr<'a> {
// future, hence do not advise calling `configure_recurse` directly.
widget.configure_recurse(self, id);
}

/// Update a text object, setting font properties and wrap size
///
/// Returns required size.
#[inline]
pub fn text_set_size(
&self,
text: &mut dyn TextApi,
class: TextClass,
size: Size,
align: (Align, Align),
) -> Vec2 {
self.sh.text_set_size(text, class, size, align)
}
}

impl<'a> std::ops::BitOrAssign<TkAction> for SetRectMgr<'a> {
Expand Down
Loading

0 comments on commit 905e877

Please sign in to comment.