diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a3cc50e3..59edd7f5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,8 +27,8 @@ jobs: cargo fmt --all -- --check - name: Build docs run: | - cargo doc --all --no-deps - cargo doc --features nightly --all --no-deps + cargo doc --all --no-deps -Zwarnings --config 'build.warnings="deny"' + cargo doc --features nightly --all --no-deps -Zwarnings --config 'build.warnings="deny"' - name: Test kas-macros run: | cargo test --manifest-path crates/kas-macros/Cargo.toml @@ -55,11 +55,11 @@ jobs: cargo test --manifest-path crates/kas-dylib/Cargo.toml --features kas-core/winit,kas-core/wayland cargo test --manifest-path crates/kas-dylib/Cargo.toml --all-features --features kas-core/winit,kas-core/x11 - name: Test kas - run: cargo test --features nightly - - name: Test kas (experimental) + run: cargo test --features nightly -Zwarnings --config 'build.warnings="deny"' + - name: Test kas (experimental; some warnings may be expected) run: cargo test --features nightly,experimental - name: Test examples/mandlebrot - run: cargo test --manifest-path examples/mandlebrot/Cargo.toml + run: cargo test --manifest-path examples/mandlebrot/Cargo.toml -Zwarnings --config 'build.warnings="deny"' test: name: Test diff --git a/Cargo.toml b/Cargo.toml index d7e386c8..75ec0f6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,18 +33,21 @@ default = ["minimal", "view", "image", "resvg", "clipboard", "markdown", "shapin # All standard test target features stable = ["default", "x11", "serde", "toml", "yaml", "json", "ron", "macros_log"] # Enables all "recommended" features for nightly rustc -nightly = ["stable"] +nightly = ["stable", "nightly-diagnostics", "kas-core/nightly"] # Additional, less recommendation-worthy features experimental = ["dark-light", "recursive-layout-widgets", "unsafe_node"] # Enable dynamic linking (faster linking via an extra run-time dependency): dynamic = ["dep:kas-dylib"] +######### optional dependencies / features ######### + +# Enables better proc-macro diagnostics (including warnings); nightly only. +nightly-diagnostics = ["kas-core/nightly-diagnostics"] + # Use full specialization spec = ["kas-core/spec"] -######### optional dependencies / features ######### - # Enable view widgets view = ["dep:kas-view"] diff --git a/crates/kas-core/Cargo.toml b/crates/kas-core/Cargo.toml index 47432be4..dadbcb2c 100644 --- a/crates/kas-core/Cargo.toml +++ b/crates/kas-core/Cargo.toml @@ -23,10 +23,13 @@ minimal = ["winit", "wayland"] # All standard test target features stable = ["minimal", "clipboard", "markdown", "shaping", "spawn", "x11", "serde", "toml", "yaml", "json", "ron", "macros_log", "image"] # Enables all "recommended" features for nightly rustc -nightly = ["stable", "kas-macros/nightly"] +nightly = ["stable", "nightly-diagnostics"] # Additional, less recommendation-worthy features experimental = ["dark-light", "recursive-layout-widgets", "unsafe_node"] +# Enables better proc-macro diagnostics (including warnings); nightly only. +nightly-diagnostics = ["kas-macros/nightly"] + # Use full specialization spec = [] diff --git a/crates/kas-core/src/core/layout.rs b/crates/kas-core/src/core/layout.rs index 2b440b7f..2941a0a6 100644 --- a/crates/kas-core/src/core/layout.rs +++ b/crates/kas-core/src/core/layout.rs @@ -232,7 +232,7 @@ pub trait Layout { /// widget tree) occupying `coord` (exceptions possible; see below). /// /// The callee may assume that it occupies `coord`. - /// Callers should prefer to call [`Tile::try_probe`] instead. + /// Callers should prefer to call [`Layout::try_probe`] instead. /// /// This method is used to determine which widget reacts to the mouse and /// touch events at the given coordinates. The widget identified by this @@ -279,7 +279,7 @@ pub trait Layout { /// /// ### Call order /// - /// It is expected that [`Tile::set_rect`] is called before this method, + /// It is expected that [`Layout::set_rect`] is called before this method, /// but failure to do so should not cause a fatal error. fn try_probe(&mut self, coord: Coord) -> Option { self.rect().contains(coord).then(|| self.probe(coord)) diff --git a/crates/kas-core/src/hidden.rs b/crates/kas-core/src/hidden.rs index af15e456..af01ab47 100644 --- a/crates/kas-core/src/hidden.rs +++ b/crates/kas-core/src/hidden.rs @@ -78,7 +78,6 @@ impl_scope! { #[autoimpl(Deref, DerefMut using self.inner)] #[autoimpl(class_traits using self.inner where W: trait)] #[derive(Clone, Default)] - #[widget{ derive = self.inner; }] pub struct MapAny> { _a: std::marker::PhantomData, pub inner: W, @@ -94,6 +93,71 @@ impl_scope! { } } + impl Layout for Self { + #[inline] + fn as_layout(&self) -> &dyn ::kas::Layout { + self + } + #[inline] + fn id_ref(&self) -> &::kas::Id { + self.inner.id_ref() + } + #[inline] + fn rect(&self) -> ::kas::geom::Rect { + self.inner.rect() + } + + #[inline] + fn widget_name(&self) -> &'static str { + "MapAny" + } + + #[inline] + fn num_children(&self) -> usize { + self.inner.num_children() + } + #[inline] + fn get_child(&self, index: usize) -> Option<&dyn ::kas::Layout> { + self.inner.get_child(index) + } + #[inline] + fn find_child_index(&self, id: &::kas::Id) -> Option { + self.inner.find_child_index(id) + } + + #[inline] + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + self.inner.size_rules(sizer, axis) + } + + #[inline] + fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { + self.inner.set_rect(cx, rect, hints); + } + + #[inline] + fn nav_next(&self, reverse: bool, from: Option) -> Option { + self.inner.nav_next(reverse, from) + } + + #[inline] + fn translation(&self) -> ::kas::geom::Offset { + self.inner.translation() + } + + // NOTE: fn probe is left unimplemented since it should not be called directly + + #[inline] + fn try_probe(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { + self.inner.try_probe(coord) + } + + #[inline] + fn draw(&mut self, draw: DrawCx) { + self.inner.draw(draw); + } + } + impl Widget for Self { type Data = A; diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 71f5c4e5..ebfcca3c 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -170,9 +170,9 @@ impl From for Directions { /// } /// impl Layout for Self { /// fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { -/// let mut rules = self.layout_visitor().size_rules(sizer, axis); -/// rules.set_stretch(Stretch::High); -/// rules +/// self.layout_visitor() +/// .size_rules(sizer, axis) +/// .with_stretch(Stretch::High) /// } /// } /// } diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 661d67cc..2da511e8 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -201,9 +201,6 @@ impl Visitor { /// This method is identical to [`Layout::size_rules`]. #[inline] pub fn size_rules(mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { - self.size_rules_(sizer, axis) - } - fn size_rules_(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { self.0.size_rules(sizer, axis) } @@ -213,9 +210,6 @@ impl Visitor { /// In other respects, this functions identically to [`Layout::set_rect`]. #[inline] pub fn set_rect(mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.set_rect_(cx, rect, hints); - } - fn set_rect_(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { self.0.set_rect(cx, rect, hints); } @@ -229,9 +223,6 @@ impl Visitor { /// 4. Otherwise return `Some(self.id())` #[inline] pub fn try_probe(mut self, coord: Coord) -> Option { - self.try_probe_(coord) - } - fn try_probe_(&mut self, coord: Coord) -> Option { self.0.try_probe(coord) } @@ -240,28 +231,25 @@ impl Visitor { /// This method is identical to [`Layout::draw`]. #[inline] pub fn draw(mut self, draw: DrawCx) { - self.draw_(draw); - } - fn draw_(&mut self, draw: DrawCx) { self.0.draw(draw); } } impl Visitable for Visitor { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { - self.size_rules_(sizer, axis) + self.0.size_rules(sizer, axis) } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.set_rect_(cx, rect, hints); + self.0.set_rect(cx, rect, hints); } fn try_probe(&mut self, coord: Coord) -> Option { - self.try_probe_(coord) + self.0.try_probe(coord) } fn draw(&mut self, draw: DrawCx) { - self.draw_(draw); + self.0.draw(draw); } } diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 072fcec9..3d4c3fb5 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -7,7 +7,7 @@ use crate::collection::{CellInfo, GridDimensions, NameGenerator, StorIdent}; use crate::widget; use crate::widget_args::{Child, ChildIdent}; use proc_macro2::{Span, TokenStream as Toks}; -use proc_macro_error2::{emit_error, emit_warning}; +use proc_macro_error2::emit_error; use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; use syn::parse::{Parse, ParseStream, Result}; use syn::spanned::Spanned; @@ -416,7 +416,7 @@ impl Layout { let _ = Pack::parse(dot_token, &input2, &mut temp_gen)?; continue; } else if let Ok(ident) = input2.parse::() { - emit_warning!( + proc_macro_error2::emit_warning!( ident, "this method call is incompatible with feature `recursive-layout-widgets`"; note = "extract operand from layout expression or wrap with braces", ); diff --git a/crates/kas-macros/src/widget_args.rs b/crates/kas-macros/src/widget_args.rs index ad4af56b..8fa09ee9 100644 --- a/crates/kas-macros/src/widget_args.rs +++ b/crates/kas-macros/src/widget_args.rs @@ -67,6 +67,12 @@ impl ToTokens for CursorIcon { } } +#[derive(Debug)] +pub struct Derive { + pub kw: kw::derive, + pub field: syn::Member, +} + #[derive(Debug)] pub struct Layout { pub kw: kw::layout, @@ -81,7 +87,7 @@ pub struct WidgetArgs { pub navigable: Option, pub hover_highlight: Option, pub cursor_icon: Option, - pub derive: Option, + pub derive: Option, pub layout: Option, } @@ -122,11 +128,12 @@ impl Parse for WidgetArgs { expr: content.parse()?, }); } else if lookahead.peek(kw::derive) && derive.is_none() { - let _ = Some(content.parse::()?); + let kw = content.parse::()?; let _: Eq = content.parse()?; let _: Token![self] = content.parse()?; let _: Token![.] = content.parse()?; - derive = Some(content.parse()?); + let field = content.parse()?; + derive = Some(Derive { kw, field }); } else if lookahead.peek(kw::layout) && layout.is_none() { layout = Some(Layout { kw: content.parse()?, diff --git a/crates/kas-macros/src/widget_derive.rs b/crates/kas-macros/src/widget_derive.rs index 55b43cb5..a691f2f9 100644 --- a/crates/kas-macros/src/widget_derive.rs +++ b/crates/kas-macros/src/widget_derive.rs @@ -22,21 +22,43 @@ use syn::Type; /// It may also inject code into existing methods such that the only observable /// behaviour is a panic. pub fn widget(_attr_span: Span, args: WidgetArgs, scope: &mut Scope) -> Result<()> { - let inner = args.derive.as_ref().unwrap(); + let derive = args.derive.unwrap(); + let derive_span = proc_macro_error2::SpanRange { + first: derive.kw.span(), + last: derive.field.span(), + } + .collapse(); + let inner = &derive.field; + if let Some(ref toks) = args.data_ty { - emit_error!(toks, "not supported by #[widget(derive=FIELD)]") + emit_error!( + toks, "not supported by #[widget(derive=FIELD)]"; + note = derive_span => "usage of derive mode"; + ) } if let Some(ref toks) = args.navigable { - emit_error!(toks, "not supported by #[widget(derive=FIELD)]") + emit_error!( + toks, "not supported by #[widget(derive=FIELD)]"; + note = derive_span => "usage of derive mode"; + ) } if let Some(ref toks) = args.hover_highlight { - emit_error!(toks.span(), "not supported by #[widget(derive=FIELD)]") + emit_error!( + toks.span(), "not supported by #[widget(derive=FIELD)]"; + note = derive_span => "usage of derive mode"; + ) } if let Some(ref toks) = args.cursor_icon { - emit_error!(toks, "not supported by #[widget(derive=FIELD)]") + emit_error!( + toks, "not supported by #[widget(derive=FIELD)]"; + note = derive_span => "usage of derive mode"; + ) } if let Some(Layout { ref kw, .. }) = args.layout { - emit_error!(kw, "not supported by #[widget(derive=FIELD)]") + emit_error!( + kw, "not supported by #[widget(derive=FIELD)]"; + note = derive_span => "usage of derive mode"; + ) } scope.expand_impl_self(); @@ -59,13 +81,17 @@ pub fn widget(_attr_span: Span, args: WidgetArgs, scope: &mut Scope) -> Result<( || *path == parse_quote! { Events } { emit_warning!( - inner, "Events impl is not used by #[widget(derive=FIELD)]"; - note = path.span() => "this Events impl"; + path, "Events impl is not used by #[widget(derive=FIELD)]"; + note = derive_span => "usage of derive mode"; ); } else if *path == parse_quote! { ::kas::Widget } || *path == parse_quote! { kas::Widget } || *path == parse_quote! { Widget } { + emit_error!( + path, "Widget impl is supported by #[widget(derive=FIELD)]"; + note = derive_span => "usage of derive mode"; + ); if widget_impl.is_none() { widget_impl = Some(index); } diff --git a/crates/kas-widgets/src/adapt/adapt_events.rs b/crates/kas-widgets/src/adapt/adapt_events.rs index 1faea2f9..f38ecb00 100644 --- a/crates/kas-widgets/src/adapt/adapt_events.rs +++ b/crates/kas-widgets/src/adapt/adapt_events.rs @@ -8,8 +8,11 @@ use super::{AdaptConfigCx, AdaptEventCx}; use kas::autoimpl; use kas::event::{ConfigCx, Event, EventCx, IsUsed}; +use kas::geom::Rect; +use kas::layout::{AlignHints, AxisInfo, SizeRules}; +use kas::theme::{DrawCx, SizeCx}; #[allow(unused)] use kas::Events; -use kas::{Id, LayoutExt, NavAdvance, Node, Widget}; +use kas::{Id, Layout, LayoutExt, NavAdvance, Node, Widget}; use std::fmt::Debug; kas::impl_scope! { @@ -18,7 +21,6 @@ kas::impl_scope! { /// This type is constructed by some [`AdaptWidget`](super::AdaptWidget) methods. #[autoimpl(Deref, DerefMut using self.inner)] #[autoimpl(Scrollable using self.inner where W: trait)] - #[widget{ derive = self.inner; }] pub struct AdaptEvents { pub inner: W, on_configure: Option>, @@ -120,6 +122,71 @@ kas::impl_scope! { } } + impl Layout for Self { + #[inline] + fn as_layout(&self) -> &dyn ::kas::Layout { + self + } + #[inline] + fn id_ref(&self) -> &::kas::Id { + self.inner.id_ref() + } + #[inline] + fn rect(&self) -> ::kas::geom::Rect { + self.inner.rect() + } + + #[inline] + fn widget_name(&self) -> &'static str { + "AdaptEvents" + } + + #[inline] + fn num_children(&self) -> usize { + self.inner.num_children() + } + #[inline] + fn get_child(&self, index: usize) -> Option<&dyn ::kas::Layout> { + self.inner.get_child(index) + } + #[inline] + fn find_child_index(&self, id: &::kas::Id) -> Option { + self.inner.find_child_index(id) + } + + #[inline] + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + self.inner.size_rules(sizer, axis) + } + + #[inline] + fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { + self.inner.set_rect(cx, rect, hints); + } + + #[inline] + fn nav_next(&self, reverse: bool, from: Option) -> Option { + self.inner.nav_next(reverse, from) + } + + #[inline] + fn translation(&self) -> ::kas::geom::Offset { + self.inner.translation() + } + + // NOTE: fn probe is left unimplemented since it should not be called directly + + #[inline] + fn try_probe(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { + self.inner.try_probe(coord) + } + + #[inline] + fn draw(&mut self, draw: DrawCx) { + self.inner.draw(draw); + } + } + impl Widget for AdaptEvents { type Data = W::Data;