Skip to content

Commit

Permalink
Merge pull request #476 from kas-gui/work3
Browse files Browse the repository at this point in the history
Replace some layout proc-macros with by-example macros over named widgets
  • Loading branch information
dhardy authored Feb 15, 2025
2 parents cfa4e62 + 875c5e8 commit 2efcc4c
Show file tree
Hide file tree
Showing 42 changed files with 1,238 additions and 918 deletions.
17 changes: 1 addition & 16 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ stable = ["default", "x11", "serde", "toml", "yaml", "json", "ron", "macros_log"
# Enables all "recommended" features for nightly rustc
nightly = ["stable", "nightly-diagnostics", "kas-core/nightly"]
# Additional, less recommendation-worthy features
experimental = ["dark-light", "recursive-layout-widgets", "unsafe_node"]
experimental = ["dark-light", "unsafe_node"]

# Enable dynamic linking (faster linking via an extra run-time dependency):
dynamic = ["dep:kas-dylib"]
Expand Down Expand Up @@ -114,21 +114,6 @@ wayland = ["kas-core/wayland"]
# Support X11
x11 = ["kas-core/x11"]

# Optimize generated layout widgets
#
# Recursive layout macros allow the generation of complex layout widgets; for
# example `row!["a", column!["b", "c"]]` yields a single layout widget (over
# three `StrLabel` widgets) instead of two separate layout widgets.
# (Note that this happens anyway in custom widget layout syntax where it is
# requried to support reference to widget fields.)
#
# A limited number of method calls such as `.align(AlignHints::LEFT)` and
# `.map_any()` are supported but are not linked correctly via rust-analyzer.
#
# Often results in unused import warnings, hence you may want to use
# RUSTFLAGS="-A unused_imports".
recursive-layout-widgets = ["kas-core/recursive-layout-widgets"]

# Optimize Node using unsafe code
unsafe_node = ["kas-core/unsafe_node"]

Expand Down
5 changes: 1 addition & 4 deletions crates/kas-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ stable = ["minimal", "clipboard", "markdown", "shaping", "spawn", "x11", "serde"
# Enables all "recommended" features for nightly rustc
nightly = ["stable", "nightly-diagnostics"]
# Additional, less recommendation-worthy features
experimental = ["dark-light", "recursive-layout-widgets", "unsafe_node"]
experimental = ["dark-light", "unsafe_node"]

# Enables better proc-macro diagnostics (including warnings); nightly only.
nightly-diagnostics = ["kas-macros/nightly"]
Expand Down Expand Up @@ -85,9 +85,6 @@ dark-light = ["dep:dark-light"]
# Support spawning async tasks
spawn = ["dep:async-global-executor"]

# Optimize generated layout widgets
recursive-layout-widgets = ["kas-macros/recursive-layout-widgets"]

# Optimize Node using unsafe code
unsafe_node = []

Expand Down
30 changes: 18 additions & 12 deletions crates/kas-core/src/core/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,9 @@ pub trait Tile: Layout {

/// Get the widget's region, relative to its parent.
///
/// This method is implemented by the `#[widget]` macro.
fn rect(&self) -> Rect {
unimplemented!() // make rustdoc show that this is a provided method
}
/// This method is usually implemented by the `#[widget]` macro.
/// See also [`kas::widget_set_rect`].
fn rect(&self) -> Rect;

/// Get the name of the widget struct
///
Expand All @@ -291,6 +290,8 @@ pub trait Tile: Layout {

/// Access a child as a `dyn Tile`
///
/// This method returns `None` exactly when `index >= self.num_children()`.
///
/// This method is usually implemented automatically by the `#[widget]`
/// macro.
fn get_child(&self, index: usize) -> Option<&dyn Tile> {
Expand All @@ -316,16 +317,21 @@ pub trait Tile: Layout {
/// Controls <kbd>Tab</kbd> navigation order of children.
/// This method should:
///
/// - Return `None` if there is no next child
/// - Determine the next child after `from` (if provided) or the whole
/// range, optionally in `reverse` order
/// - Ensure that the selected widget is addressable through
/// [`Tile::get_child`]
/// - Return `None` if there is no (next) navigable child
/// - In the case there are navigable children and `from == None`, return
/// the index of the first (or last if `reverse`) navigable child
/// - In the case there are navigable children and `from == Some(index)`,
/// it may be expected that `from` is the output of a previous call to
/// this method; the method should return the next (or previous if
/// `reverse`) navigable child (if any)
///
/// The return value mut be `None` or `Some(index)` where
/// `self.get_child(index).is_some()` (see [`Tile::get_child`]).
///
/// Both `from` and the return value use the widget index, as used by
/// [`Tile::get_child`].
/// It is not required that all children (all indices `i` for
/// `i < self.num_children()`) are returnable from this method.
///
/// Default implementation:
/// Default (macro generated) implementation:
///
/// - Generated from `#[widget]`'s layout property, if used (not always possible!)
/// - Otherwise, iterate through children in order of definition
Expand Down
2 changes: 1 addition & 1 deletion crates/kas-core/src/draw/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ impl From<[u8; 4]> for Rgba8Srgb {
#[derive(Copy, Clone, Debug, Error)]
pub enum ParseError {
/// Incorrect input length
#[error("invalid length (expected 6 or 8 bytes")]
#[error("invalid length (expected 6 or 8 bytes)")]
Length,
/// Invalid hex byte
#[error("invalid hex byte (expected 0-9, a-f or A-F)")]
Expand Down
80 changes: 77 additions & 3 deletions crates/kas-core/src/hidden.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
//! not supported (i.e. **changes are not considered breaking**).
use crate::event::ConfigCx;
use crate::geom::Rect;
use crate::layout::AlignHints;
use crate::theme::{Text, TextClass};
use crate::geom::{Rect, Size};
use crate::layout::{AlignHints, AxisInfo, SizeRules};
use crate::theme::{SizeCx, Text, TextClass};
#[allow(unused)] use crate::Action;
use crate::{Events, Layout, Widget};
use kas_macros::{autoimpl, impl_scope, widget_set_rect};

Expand Down Expand Up @@ -94,3 +95,76 @@ impl_scope! {
}
}
}

impl_scope! {
/// Apply an alignment hint
///
/// The inner widget chooses how to apply (or ignore) this hint.
///
/// Usually, this type will be constructed through one of the methods on
/// [`AdaptWidget`](https://docs.rs/kas/latest/kas/widgets/trait.AdaptWidget.html).
#[widget{ derive = self.inner; }]
pub struct Align<W: Widget> {
pub inner: W,
/// Hints may be modified directly.
///
/// Use [`Action::RESIZE`] to apply changes.
pub hints: AlignHints,
}

impl Self {
/// Construct
#[inline]
pub fn new(inner: W, hints: AlignHints) -> Self {
Align { inner, hints }
}
}

impl Layout for Self {
fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
self.inner.set_rect(cx, rect, self.hints.combine(hints));
}
}
}

impl_scope! {
/// Apply an alignment hint, squash and align the result
///
/// The inner widget chooses how to apply (or ignore) this hint.
/// The widget is then prevented from stretching beyond its ideal size,
/// aligning within the available rect.
///
/// Usually, this type will be constructed through one of the methods on
/// [`AdaptWidget`](https://docs.rs/kas/latest/kas/widgets/trait.AdaptWidget.html).
#[widget{ derive = self.inner; }]
pub struct Pack<W: Widget> {
pub inner: W,
/// Hints may be modified directly.
///
/// Use [`Action::RESIZE`] to apply changes.
pub hints: AlignHints,
size: Size,
}

impl Self {
/// Construct
#[inline]
pub fn new(inner: W, hints: AlignHints) -> Self {
Pack { inner, hints, size: Size::ZERO }
}
}

impl Layout for Self {
fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
let rules = self.inner.size_rules(sizer, axis);
self.size.set_component(axis, rules.ideal_size());
rules
}

fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
let align = self.hints.combine(hints).complete_default();
let rect = align.aligned_rect(self.size, rect);
self.inner.set_rect(cx, rect, hints);
}
}
}
4 changes: 2 additions & 2 deletions crates/kas-core/src/layout/align.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ impl AlignHints {
/// Top, left
pub const TOP_LEFT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::TL));
/// Top, right
pub const TOP_RIGHT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::BR));
pub const TOP_RIGHT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::TL));
/// Bottom, left
pub const BOTTOM_LEFT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::TL));
pub const BOTTOM_LEFT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::BR));
/// Bottom, right
pub const BOTTOM_RIGHT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::BR));

Expand Down
5 changes: 4 additions & 1 deletion crates/kas-core/src/layout/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,13 @@ impl<'a> Visitor<Box<dyn Layout + 'a>> {
storage: &'a mut FrameStorage,
child: C,
style: FrameStyle,
bg: Background,
) -> Visitor<impl Layout + 'a> {
Visitor(Frame {
child,
storage,
style,
bg,
})
}

Expand Down Expand Up @@ -340,6 +342,7 @@ struct Frame<'a, C: Layout> {
child: C,
storage: &'a mut FrameStorage,
style: FrameStyle,
bg: Background,
}

impl<'a, C: Layout> Layout for Frame<'a, C> {
Expand All @@ -365,7 +368,7 @@ impl<'a, C: Layout> Layout for Frame<'a, C> {
}

fn draw(&mut self, mut draw: DrawCx) {
draw.frame(self.storage.rect, self.style, Background::Default);
draw.frame(self.storage.rect, self.style, self.bg);
self.child.draw(draw);
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/kas-core/src/popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl_scope! {
///
/// A popup receives input data from its parent like any other widget.
#[widget {
layout = frame!(self.inner, style = kas::theme::FrameStyle::Popup);
layout = frame!(self.inner).with_style(kas::theme::FrameStyle::Popup);
}]
pub struct Popup<W: Widget> {
core: widget_core!(),
Expand Down
35 changes: 34 additions & 1 deletion crates/kas-core/src/theme/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use super::{FrameStyle, MarkStyle, SelectionStyle, SizeCx, Text, TextClass, ThemeSize};
use crate::dir::Direction;
use crate::draw::color::Rgb;
use crate::draw::color::{ParseError, Rgb};
use crate::draw::{Draw, DrawIface, DrawShared, DrawSharedImpl, ImageId, PassType};
use crate::event::{ConfigCx, EventState};
use crate::geom::{Offset, Rect};
Expand All @@ -29,6 +29,39 @@ pub enum Background {
Rgb(Rgb),
}

impl From<Rgb> for Background {
#[inline]
fn from(color: Rgb) -> Self {
Background::Rgb(color)
}
}

#[derive(Copy, Clone, Debug, thiserror::Error)]
pub enum BackgroundParseError {
/// No `#` prefix
///
/// NOTE: this exists to allow the possibility of supporting new exprs like
/// "Default" or "Error".
#[error("Unknown: no `#` prefix")]
Unknown,
/// Invalid hex
#[error("invalid hex sequence")]
InvalidRgb(#[from] ParseError),
}

impl std::str::FromStr for Background {
type Err = BackgroundParseError;

#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("#") {
Rgb::from_str(s).map(|c| c.into()).map_err(|e| e.into())
} else {
Err(BackgroundParseError::Unknown)
}
}
}

/// Draw interface
///
/// This interface is provided to widgets in [`crate::Layout::draw`].
Expand Down
2 changes: 1 addition & 1 deletion crates/kas-core/src/theme/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl<T: FormattableText> Layout for Text<T> {
}

fn draw(&mut self, mut draw: DrawCx) {
draw.text(self.rect, &self);
draw.text(self.rect, self);
}
}

Expand Down
3 changes: 0 additions & 3 deletions crates/kas-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ proc-macro = true
# Requires that all crates using these macros depend on the log crate.
log = []

# Optimize generated layout widgets
recursive-layout-widgets = []

# Enable reporting of warnings from proc-macros
nightly = ["proc-macro-error2/nightly"]

Expand Down
Loading

0 comments on commit 2efcc4c

Please sign in to comment.