Subpixels + Feathering + figures refactor

Much curves
This commit is contained in:
Jonathan Johnson 2023-11-19 10:34:06 -08:00
parent aea9def07d
commit d5bde44e27
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
20 changed files with 144 additions and 155 deletions

8
Cargo.lock generated
View file

@ -623,7 +623,7 @@ dependencies = [
[[package]] [[package]]
name = "figures" name = "figures"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/khonsulabs/figures#4712514fbed861ad530bd48d4e62f8efcaa4df1f" source = "git+https://github.com/khonsulabs/figures#0745fc0ff95e94ce2181aa1528a30bda3d6152f2"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"euclid", "euclid",
@ -1089,7 +1089,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]] [[package]]
name = "kludgine" name = "kludgine"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/khonsulabs/kludgine#ca138e79a4b8ebc3bdd463006b1dadd3269183f3" source = "git+https://github.com/khonsulabs/kludgine#0530299bbe79431b5fa398182991c63e06d94b90"
dependencies = [ dependencies = [
"ahash", "ahash",
"alot", "alot",
@ -1974,9 +1974,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.24" version = "0.38.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234" checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
dependencies = [ dependencies = [
"bitflags 2.4.1", "bitflags 2.4.1",
"errno", "errno",

View file

@ -1,7 +1,6 @@
use gooey::widgets::Canvas; use gooey::widgets::Canvas;
use gooey::{Run, Tick}; use gooey::{Run, Tick};
use kludgine::figures::units::Px; use kludgine::figures::{Angle, IntoSigned, Point, Px2D, Rect, Size};
use kludgine::figures::{Angle, IntoSigned, Point, Rect, Size};
use kludgine::shapes::Shape; use kludgine::shapes::Shape;
use kludgine::text::{Text, TextOrigin}; use kludgine::text::{Text, TextOrigin};
use kludgine::{Color, DrawableExt}; use kludgine::{Color, DrawableExt};
@ -15,11 +14,11 @@ fn main() -> gooey::Result<()> {
context.gfx.draw_text( context.gfx.draw_text(
Text::new("Canvas exposes the full power of Kludgine", Color::WHITE) Text::new("Canvas exposes the full power of Kludgine", Color::WHITE)
.origin(TextOrigin::Center) .origin(TextOrigin::Center)
.translate_by(center - Point::new(Px(0), Px(100))), .translate_by(center - Point::px(0, 100)),
); );
context.gfx.draw_shape( context.gfx.draw_shape(
Shape::filled_rect( Shape::filled_rect(
Rect::new(Point::new(Px(-50), Px(-50)), Size::new(Px(100), Px(100))), Rect::new(Point::px(-50, -50), Size::px(100, 100)),
Color::RED, Color::RED,
) )
.translate_by(center) .translate_by(center)

View file

@ -13,7 +13,7 @@ fn main() -> gooey::Result {
.and("Masked Input Field:") .and("Masked Input Field:")
.and(password.into_input()) .and(password.into_input())
.into_rows() .into_rows()
.width(Px(100)..Px(800)) .width(Px::new(100)..Px::new(800))
.scroll() .scroll()
.centered() .centered()
.expand() .expand()

View file

@ -8,9 +8,10 @@ use gooey::kludgine::Color;
use gooey::value::Dynamic; use gooey::value::Dynamic;
use gooey::widgets::TileMap; use gooey::widgets::TileMap;
use gooey::{Run, Tick}; use gooey::{Run, Tick};
use kludgine::figures::FloatConversion;
use kludgine::DrawableExt; use kludgine::DrawableExt;
const PLAYER_SIZE: Px = Px(16); const PLAYER_SIZE: Px = Px::new(16);
#[rustfmt::skip] #[rustfmt::skip]
const TILES: [TileKind; 64] = { const TILES: [TileKind; 64] = {
@ -33,7 +34,7 @@ fn main() -> gooey::Result {
let myself = characters.push(Player { let myself = characters.push(Player {
color: Color::RED, color: Color::RED,
position: Point::new(TILE_SIZE.0 as f32, TILE_SIZE.0 as f32), position: Point::new(TILE_SIZE.into_float(), TILE_SIZE.into_float()),
}); });
let layers = Dynamic::new((Tiles::new(8, 8, TILES), characters)); let layers = Dynamic::new((Tiles::new(8, 8, TILES), characters));
@ -58,10 +59,11 @@ fn main() -> gooey::Result {
direction.x -= 1.0; direction.x -= 1.0;
} }
let one_second_movement = direction * TILE_SIZE.0 as f32; let one_second_movement = direction * TILE_SIZE.into_float();
layers.map_mut(|layers| { layers.map_mut(|layers| {
layers.1[myself].position += Point::new( layers.1[myself].position += Point::new(
// TODO fix this in figures
one_second_movement.x * elapsed.as_secs_f32(), one_second_movement.x * elapsed.as_secs_f32(),
one_second_movement.y * elapsed.as_secs_f32(), one_second_movement.y * elapsed.as_secs_f32(),
) )
@ -85,10 +87,7 @@ impl Object for Player {
let zoomed_size = PLAYER_SIZE * zoom; let zoomed_size = PLAYER_SIZE * zoom;
context.draw_shape( context.draw_shape(
Shape::filled_rect( Shape::filled_rect(
Rect::new( Rect::new(Point::squared(-zoomed_size / 2), Size::squared(zoomed_size)),
Point::new(-zoomed_size / 2, -zoomed_size / 2),
Size::squared(zoomed_size),
),
self.color, self.color,
) )
.translate_by(center), .translate_by(center),

View file

@ -10,7 +10,7 @@ use kludgine::app::winit::event::{
DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
}; };
use kludgine::figures::units::{Lp, Px, UPx}; use kludgine::figures::units::{Lp, Px, UPx};
use kludgine::figures::{IntoSigned, IsZero, Point, Rect, ScreenScale, Size}; use kludgine::figures::{IntoSigned, Point, Px2D, Rect, ScreenScale, Size, Zero};
use kludgine::shapes::{Shape, StrokeOptions}; use kludgine::shapes::{Shape, StrokeOptions};
use kludgine::{Color, Kludgine}; use kludgine::{Color, Kludgine};
@ -525,7 +525,7 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
/// If the alpha channel of `color` is 0, this function does nothing. /// If the alpha channel of `color` is 0, this function does nothing.
pub fn fill(&mut self, color: Color) { pub fn fill(&mut self, color: Color) {
if color.alpha() > 0 { if color.alpha() > 0 {
let visible_rect = Rect::from(self.gfx.region().size - (Px(1), Px(1))); let visible_rect = Rect::from(self.gfx.region().size - Size::px(1, 1));
let radii = self.get(&CornerRadius); let radii = self.get(&CornerRadius);
let radii = radii.map(|r| r.into_px(self.gfx.scale())); let radii = radii.map(|r| r.into_px(self.gfx.scale()));
@ -542,23 +542,23 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
/// Strokes an outline around this widget's contents. /// Strokes an outline around this widget's contents.
pub fn stroke_outline<Unit>(&mut self, color: Color, options: StrokeOptions<Unit>) pub fn stroke_outline<Unit>(&mut self, color: Color, options: StrokeOptions<Unit>)
where where
Unit: ScreenScale<Px = Px, Lp = Lp, UPx = UPx> + IsZero, Unit: ScreenScale<Px = Px, Lp = Lp, UPx = UPx> + Zero,
{ {
if color.alpha() > 0 { if color.alpha() > 0 {
let visible_rect = Rect::from(self.gfx.region().size - (Px(1), Px(1))); let options = options.colored(color).into_px(self.gfx.scale());
let inset = options.line_width;
let visible_rect = Rect::new(
Point::squared(inset),
self.gfx.region().size - Point::new(inset * 2, inset * 2),
);
let radii = self.get(&CornerRadius); let radii = self.get(&CornerRadius);
let radii = radii.map(|r| r.into_px(self.gfx.scale())); let radii = radii.map(|r| r.into_px(self.gfx.scale()));
let focus_ring = if radii.is_zero() { let focus_ring = if radii.is_zero() {
Shape::stroked_rect(visible_rect, color, options.into_px(self.gfx.scale())) Shape::stroked_rect(visible_rect, options.into_px(self.gfx.scale()))
} else { } else {
Shape::stroked_round_rect( Shape::stroked_round_rect(visible_rect, radii, options)
visible_rect,
radii,
color,
options.into_px(self.gfx.scale()),
)
}; };
self.gfx.draw_shape(&focus_ring); self.gfx.draw_shape(&focus_ring);
} }

View file

@ -2,7 +2,7 @@ use std::ops::{Deref, DerefMut};
use kludgine::figures::units::{Px, UPx}; use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{ use kludgine::figures::{
self, Fraction, IntoSigned, IntoUnsigned, IsZero, Point, Rect, ScreenScale, ScreenUnit, Size, self, Fraction, IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, ScreenUnit, Size, Zero,
}; };
use kludgine::render::Renderer; use kludgine::render::Renderer;
use kludgine::shapes::Shape; use kludgine::shapes::Shape;
@ -45,12 +45,12 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
let clip_origin = self.renderer.clip_rect().origin.into_signed(); let clip_origin = self.renderer.clip_rect().origin.into_signed();
-Point::new( -Point::new(
if clip_origin.x <= self.region.origin.x { if clip_origin.x <= self.region.origin.x {
Px(0) Px::ZERO
} else { } else {
clip_origin.x - self.region.origin.x clip_origin.x - self.region.origin.x
}, },
if clip_origin.y <= self.region.origin.y { if clip_origin.y <= self.region.origin.y {
Px(0) Px::ZERO
} else { } else {
clip_origin.y - self.region.origin.y clip_origin.y - self.region.origin.y
}, },
@ -147,7 +147,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
/// Draws a shape at the origin, rotating and scaling as needed. /// Draws a shape at the origin, rotating and scaling as needed.
pub fn draw_shape<'a, Unit>(&mut self, shape: impl Into<Drawable<&'a Shape<Unit, false>, Unit>>) pub fn draw_shape<'a, Unit>(&mut self, shape: impl Into<Drawable<&'a Shape<Unit, false>, Unit>>)
where where
Unit: IsZero + ShaderScalable + figures::ScreenUnit + Copy, Unit: Zero + ShaderScalable + figures::ScreenUnit + Copy,
{ {
let mut shape = shape.into(); let mut shape = shape.into();
shape.translation += Point::<Unit>::from_px(self.translation(), self.scale()); shape.translation += Point::<Unit>::from_px(self.translation(), self.scale());
@ -171,7 +171,7 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
shape: impl Into<Drawable<&'shape Shape, Unit>>, shape: impl Into<Drawable<&'shape Shape, Unit>>,
texture: &impl TextureSource, texture: &impl TextureSource,
) where ) where
Unit: IsZero + ShaderScalable + figures::ScreenUnit + Copy, Unit: Zero + ShaderScalable + figures::ScreenUnit + Copy,
i32: From<<Unit as IntoSigned>::Signed>, i32: From<<Unit as IntoSigned>::Signed>,
Shape: ShapeSource<Unit, true> + 'shape, Shape: ShapeSource<Unit, true> + 'shape,
{ {

View file

@ -25,7 +25,7 @@ use std::ops::Sub;
pub use kludgine; pub use kludgine;
use kludgine::app::winit::error::EventLoopError; use kludgine::app::winit::error::EventLoopError;
use kludgine::figures::units::UPx; use kludgine::figures::units::UPx;
use kludgine::figures::{Fraction, ScreenUnit}; use kludgine::figures::{Fraction, ScreenUnit, Size};
pub use names::Name; pub use names::Name;
pub use utils::{Lazy, WithClone}; pub use utils::{Lazy, WithClone};
@ -68,6 +68,27 @@ impl ConstraintLimit {
} }
} }
/// An extension trait for `Size<ConstraintLimit>`.
pub trait FitMeasuredSize {
/// Returns the result of calling [`ConstraintLimit::fit_measured`] for each
/// matching component in `self` and `measured`.
fn fit_measured<Unit>(self, measured: Size<Unit>, scale: Fraction) -> Size<UPx>
where
Unit: ScreenUnit;
}
impl FitMeasuredSize for Size<ConstraintLimit> {
fn fit_measured<Unit>(self, measured: Size<Unit>, scale: Fraction) -> Size<UPx>
where
Unit: ScreenUnit,
{
Size::new(
self.width.fit_measured(measured.width, scale),
self.height.fit_measured(measured.height, scale),
)
}
}
impl Sub<UPx> for ConstraintLimit { impl Sub<UPx> for ConstraintLimit {
type Output = Self; type Output = Self;

View file

@ -12,7 +12,7 @@ use std::sync::Arc;
use ahash::AHashMap; use ahash::AHashMap;
use kludgine::figures::units::{Lp, Px, UPx}; use kludgine::figures::units::{Lp, Px, UPx};
use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, IsZero, Rect, ScreenScale, Size}; use kludgine::figures::{Fraction, IntoSigned, IntoUnsigned, Rect, ScreenScale, Size, Zero};
use kludgine::shapes::CornerRadii; use kludgine::shapes::CornerRadii;
use kludgine::Color; use kludgine::Color;
use palette::{IntoColor, Okhsl, OklabHue, Srgb}; use palette::{IntoColor, Okhsl, OklabHue, Srgb};
@ -399,11 +399,6 @@ pub enum Dimension {
Lp(Lp), Lp(Lp),
} }
impl Dimension {
/// A dimension of 0 pixels.
pub const ZERO: Self = Self::Px(Px(0));
}
impl Default for Dimension { impl Default for Dimension {
fn default() -> Self { fn default() -> Self {
Self::ZERO Self::ZERO
@ -422,7 +417,9 @@ impl From<Lp> for Dimension {
} }
} }
impl IsZero for Dimension { impl Zero for Dimension {
const ZERO: Self = Dimension::Px(Px::ZERO);
fn is_zero(&self) -> bool { fn is_zero(&self) -> bool {
match self { match self {
Dimension::Px(x) => x.is_zero(), Dimension::Px(x) => x.is_zero(),

View file

@ -759,7 +759,7 @@ pub trait MakeWidget: Sized {
/// Returns a new widget that renders `color` behind `self`. /// Returns a new widget that renders `color` behind `self`.
fn background_color(self, color: impl IntoValue<Color>) -> Container { fn background_color(self, color: impl IntoValue<Color>) -> Container {
self.contain().pad_by(Px(0)).background_color(color) self.contain().pad_by(Px::ZERO).background_color(color)
} }
/// Wraps `self` with the default padding. /// Wraps `self` with the default padding.

View file

@ -186,10 +186,7 @@ impl WrapperWidget for Align {
let layout = self.measure(available_space, context); let layout = self.measure(available_space, context);
Rect::new( Rect::new(
Point::new( Point::new(layout.margin.left, layout.margin.top).into_signed(),
layout.margin.left.into_signed(),
layout.margin.top.into_signed(),
),
layout.content.into_signed(), layout.content.into_signed(),
) )
.into() .into()

View file

@ -20,6 +20,7 @@ use crate::styles::{ColorExt, Styles};
use crate::utils::ModifiersExt; use crate::utils::ModifiersExt;
use crate::value::{Dynamic, IntoValue, Value}; use crate::value::{Dynamic, IntoValue, Value};
use crate::widget::{Callback, EventHandling, MakeWidget, Widget, WidgetRef, HANDLED, IGNORED}; use crate::widget::{Callback, EventHandling, MakeWidget, Widget, WidgetRef, HANDLED, IGNORED};
use crate::FitMeasuredSize;
/// A clickable button. /// A clickable button.
#[derive(Debug)] #[derive(Debug)]
@ -362,12 +363,8 @@ impl Widget for Button {
let inset = context.get(&IntrinsicPadding).into_px(context.gfx.scale()); let inset = context.get(&IntrinsicPadding).into_px(context.gfx.scale());
let focus_ring = Shape::stroked_rect( let focus_ring = Shape::stroked_rect(
Rect::new( Rect::new(Point::squared(inset), context.gfx.region().size - inset * 2),
Point::new(inset, inset), two_lp_stroke.colored(color),
context.gfx.region().size - inset * 2,
),
color,
two_lp_stroke,
); );
context.gfx.draw_shape(&focus_ring); context.gfx.draw_shape(&focus_ring);
} else if context.is_default() { } else if context.is_default() {
@ -452,22 +449,12 @@ impl Widget for Button {
let padding = context.get(&IntrinsicPadding).into_upx(context.gfx.scale()); let padding = context.get(&IntrinsicPadding).into_upx(context.gfx.scale());
let double_padding = padding * 2; let double_padding = padding * 2;
let mounted = self.content.mounted(&mut context.as_event_context()); let mounted = self.content.mounted(&mut context.as_event_context());
let available_space = Size::new( let available_space = available_space.map(|space| space - double_padding);
available_space.width - double_padding,
available_space.height - double_padding,
);
let size = context.for_other(&mounted).layout(available_space); let size = context.for_other(&mounted).layout(available_space);
let size = Size::new( let size = available_space.fit_measured(size, context.gfx.scale());
available_space
.width
.fit_measured(size.width, context.gfx.scale()),
available_space
.height
.fit_measured(size.height, context.gfx.scale()),
);
context.set_child_layout( context.set_child_layout(
&mounted, &mounted,
Rect::new(Point::new(padding, padding), size).into_signed(), Rect::new(Point::squared(padding), size).into_signed(),
); );
size + double_padding size + double_padding
} }

View file

@ -7,7 +7,7 @@ use kludgine::figures::Size;
use crate::context::{GraphicsContext, LayoutContext}; use crate::context::{GraphicsContext, LayoutContext};
use crate::value::Dynamic; use crate::value::Dynamic;
use crate::widget::Widget; use crate::widget::Widget;
use crate::Tick; use crate::{ConstraintLimit, Tick};
/// A 2d drawable surface. /// A 2d drawable surface.
#[must_use] #[must_use]
@ -55,7 +55,7 @@ impl Widget for Canvas {
available_space: Size<crate::ConstraintLimit>, available_space: Size<crate::ConstraintLimit>,
_context: &mut LayoutContext<'_, '_, '_, '_, '_>, _context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> Size<UPx> { ) -> Size<UPx> {
Size::new(available_space.width.max(), available_space.height.max()) available_space.map(ConstraintLimit::max)
} }
} }

View file

@ -162,7 +162,7 @@ impl WrapperWidget for CheckboxLabel {
let label_inset = checkbox_size + padding; let label_inset = checkbox_size + padding;
let size_with_checkbox = Size::new(size.width + label_inset, size.height).into_unsigned(); let size_with_checkbox = Size::new(size.width + label_inset, size.height).into_unsigned();
WrappedLayout { WrappedLayout {
child: Rect::new(Point::new(label_inset, Px(0)), size), child: Rect::new(Point::new(label_inset, Px::ZERO), size),
size: size_with_checkbox, size: size_with_checkbox,
} }
} }
@ -170,10 +170,7 @@ impl WrapperWidget for CheckboxLabel {
fn redraw_background(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { fn redraw_background(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
let checkbox_size = context.get(&LineHeight).into_px(context.gfx.scale()); let checkbox_size = context.get(&LineHeight).into_px(context.gfx.scale());
let padding = context.get(&IntrinsicPadding).into_px(context.gfx.scale()); let padding = context.get(&IntrinsicPadding).into_px(context.gfx.scale());
let checkbox_rect = Rect::new( let checkbox_rect = Rect::new(Point::squared(padding), Size::squared(checkbox_size));
Point::new(padding, padding),
Size::new(checkbox_size, checkbox_size),
);
let stroke_options = StrokeOptions::lp_wide(Lp::points(2)).into_px(context.gfx.scale()); let stroke_options = StrokeOptions::lp_wide(Lp::points(2)).into_px(context.gfx.scale());
match self.value.get_tracking_refresh(context) { match self.value.get_tracking_refresh(context) {
state @ (CheckboxState::Checked | CheckboxState::Indeterminant) => { state @ (CheckboxState::Checked | CheckboxState::Indeterminant) => {
@ -196,7 +193,7 @@ impl WrapperWidget for CheckboxLabel {
icon_area.origin.y, icon_area.origin.y,
)) ))
.build() .build()
.stroke(text_color, stroke_options), .stroke(stroke_options.colored(text_color)),
); );
} else { } else {
context.gfx.draw_shape( context.gfx.draw_shape(
@ -206,15 +203,16 @@ impl WrapperWidget for CheckboxLabel {
center.y, center.y,
)) ))
.build() .build()
.stroke(text_color, stroke_options), .stroke(stroke_options.colored(text_color)),
); );
} }
} }
CheckboxState::Unchecked => { CheckboxState::Unchecked => {
let color = context.get(&OutlineColor); let color = context.get(&OutlineColor);
context context.gfx.draw_shape(&Shape::stroked_rect(
.gfx checkbox_rect,
.draw_shape(&Shape::stroked_rect(checkbox_rect, color, stroke_options)); stroke_options.colored(color),
));
} }
} }
} }

View file

@ -102,10 +102,7 @@ impl WrapperWidget for Expand {
available_space: Size<ConstraintLimit>, available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_, '_>, context: &mut LayoutContext<'_, '_, '_, '_, '_>,
) -> WrappedLayout { ) -> WrappedLayout {
let available_space = Size::new( let available_space = available_space.map(|lim| ConstraintLimit::Fill(lim.max()));
ConstraintLimit::Fill(available_space.width.max()),
ConstraintLimit::Fill(available_space.height.max()),
);
let child = self.child.mounted(&mut context.as_event_context()); let child = self.child.mounted(&mut context.as_event_context());
let size = context.for_other(&child).layout(available_space); let size = context.for_other(&child).layout(available_space);

View file

@ -522,7 +522,7 @@ where
) -> (Point<Px>, Px) { ) -> (Point<Px>, Px) {
if measured.glyphs.is_empty() || (cursor.offset == 0 && cursor.affinity == Affinity::Before) if measured.glyphs.is_empty() || (cursor.offset == 0 && cursor.affinity == Affinity::Before)
{ {
return (Point::default(), Px(0)); return (Point::default(), Px::ZERO);
} }
// Space between glyphs isn't represented in the glyphs. If the cursor rests // Space between glyphs isn't represented in the glyphs. If the cursor rests
@ -553,7 +553,7 @@ where
) { ) {
(Ordering::Less | Ordering::Equal, Ordering::Less) => { (Ordering::Less | Ordering::Equal, Ordering::Less) => {
// cosmic text may have grouped multiple graphemes into a single glyph. // cosmic text may have grouped multiple graphemes into a single glyph.
let mut grapheme_offset = Px(0); let mut grapheme_offset = Px::ZERO;
if glyph.info.start < cursor.offset { if glyph.info.start < cursor.offset {
let clustered_bytes = glyph.info.end - glyph.info.start; let clustered_bytes = glyph.info.end - glyph.info.start;
if clustered_bytes > 1 { if clustered_bytes > 1 {
@ -567,8 +567,8 @@ where
return ( return (
Point::new( Point::new(
rect.origin.x + grapheme_offset, rect.origin.x + grapheme_offset,
measured.line_height.saturating_mul(Px( measured.line_height.saturating_mul(Px::new(
i32::try_from(glyph.info.line).unwrap_or(i32::MAX) i32::try_from(glyph.info.line).unwrap_or(i32::MAX),
)), )),
), ),
rect.size.width, rect.size.width,
@ -586,9 +586,9 @@ where
if closest_after_index == usize::MAX { if closest_after_index == usize::MAX {
let bottom_right = &measured.glyphs[bottom_right_index]; let bottom_right = &measured.glyphs[bottom_right_index];
let bottom_y = measured let bottom_y = measured.line_height.saturating_mul(Px::new(
.line_height i32::try_from(bottom_right.info.line).unwrap_or(i32::MAX),
.saturating_mul(Px(i32::try_from(bottom_right.info.line).unwrap_or(i32::MAX))); ));
// No glyph could be found that started/contained the cursors offset. // No glyph could be found that started/contained the cursors offset.
let mut bottom_right_cursor = Point::new( let mut bottom_right_cursor = Point::new(
bottom_right_rect.origin.x + bottom_right_rect.size.width, bottom_right_rect.origin.x + bottom_right_rect.size.width,
@ -609,7 +609,7 @@ where
} }
// The cursor should be placed after the bottom_right glyph // The cursor should be placed after the bottom_right glyph
(bottom_right_cursor, Px(0)) (bottom_right_cursor, Px::ZERO)
} else { } else {
let before = &measured.glyphs[closest_before_index]; let before = &measured.glyphs[closest_before_index];
let after = &measured.glyphs[closest_after_index]; let after = &measured.glyphs[closest_after_index];
@ -617,7 +617,7 @@ where
let after_rect = after.rect(); let after_rect = after.rect();
let before_y = measured let before_y = measured
.line_height .line_height
.saturating_mul(Px(i32::try_from(before.info.line).unwrap_or(i32::MAX))); .saturating_mul(Px::new(i32::try_from(before.info.line).unwrap_or(i32::MAX)));
if before.info.line == after.info.line { if before.info.line == after.info.line {
let before_right = before_rect.origin.x + before_rect.size.width; let before_right = before_rect.origin.x + before_rect.size.width;
@ -639,7 +639,10 @@ where
origin.x += before_rect.size.width; origin.x += before_rect.size.width;
(origin, before_y) (origin, before_y)
} }
Affinity::After => (Point::new(Px(0), before_y + measured.line_height), Px(0)), Affinity::After => (
Point::new(Px::ZERO, before_y + measured.line_height),
Px::ZERO,
),
} }
} }
} }
@ -665,7 +668,7 @@ where
let mut closest: Option<(Cursor, i32)> = None; let mut closest: Option<(Cursor, i32)> = None;
let mut current_line = usize::MAX; let mut current_line = usize::MAX;
let mut current_line_y = Px(0); let mut current_line_y = Px::ZERO;
for glyph in &cache.measured.glyphs { for glyph in &cache.measured.glyphs {
if current_line != glyph.info.line { if current_line != glyph.info.line {
current_line = glyph.info.line; current_line = glyph.info.line;
@ -673,7 +676,7 @@ where
current_line_y = cache current_line_y = cache
.measured .measured
.line_height .line_height
.saturating_mul(Px(i32::try_from(current_line).unwrap_or(i32::MAX))); .saturating_mul(Px::new(i32::try_from(current_line).unwrap_or(i32::MAX)));
} }
let rect = glyph.rect(); let rect = glyph.rect();
let relative = location - Point::new(rect.origin.x, current_line_y); let relative = location - Point::new(rect.origin.x, current_line_y);
@ -704,7 +707,7 @@ where
// Make relative be relative to the center of the glyph for a nearest search. // Make relative be relative to the center of the glyph for a nearest search.
let relative = relative + rect.size / 2; let relative = relative + rect.size / 2;
let xy = (relative.x.0.saturating_mul(relative.y.0)).saturating_abs(); let xy = (relative.x.get().saturating_mul(relative.y.get())).saturating_abs();
match closest { match closest {
Some((_, closest_xy)) if xy < closest_xy => { Some((_, closest_xy)) if xy < closest_xy => {
closest = Some(( closest = Some((
@ -872,7 +875,7 @@ where
context.gfx.draw_shape( context.gfx.draw_shape(
Shape::filled_rect( Shape::filled_rect(
Rect::new( Rect::new(
Point::new(Px(0), bottom_of_first_line), Point::new(Px::ZERO, bottom_of_first_line),
Size::new(size.width.into_signed(), distance_between), Size::new(size.width.into_signed(), distance_between),
), ),
highlight, highlight,
@ -884,7 +887,7 @@ where
context.gfx.draw_shape( context.gfx.draw_shape(
Shape::filled_rect( Shape::filled_rect(
Rect::new( Rect::new(
Point::new(Px(0), end_position.y), Point::new(Px::ZERO, end_position.y),
Size::new(end_position.x + end_width, cache.measured.line_height), Size::new(end_position.x + end_width, cache.measured.line_height),
), ),
highlight, highlight,
@ -939,7 +942,7 @@ where
let cache = self.layout_text(Some(width.into_signed()), &mut context.graphics); let cache = self.layout_text(Some(width.into_signed()), &mut context.graphics);
cache.measured.size.into_unsigned() + Size::new(padding * 2, padding * 2) cache.measured.size.into_unsigned() + Size::squared(padding * 2)
} }
fn keyboard_input( fn keyboard_input(

View file

@ -98,10 +98,7 @@ impl WrapperWidget for Resize {
let size = if let (Some(width), Some(height)) = let size = if let (Some(width), Some(height)) =
(self.width.exact_dimension(), self.height.exact_dimension()) (self.width.exact_dimension(), self.height.exact_dimension())
{ {
Size::new( Size::new(width, height).map(|i| i.into_upx(context.gfx.scale()))
width.into_upx(context.gfx.scale()),
height.into_upx(context.gfx.scale()),
)
} else { } else {
let available_space = Size::new( let available_space = Size::new(
override_constraint(available_space.width, self.width, context.gfx.scale()), override_constraint(available_space.width, self.width, context.gfx.scale()),

View file

@ -186,7 +186,7 @@ impl Widget for Scroll {
let max_scroll_x = if self.enabled.x { let max_scroll_x = if self.enabled.x {
-self.horizontal_bar.amount_hidden -self.horizontal_bar.amount_hidden
} else { } else {
Px(0) Px::ZERO
}; };
self.vertical_bar = self.vertical_bar =
@ -194,7 +194,7 @@ impl Widget for Scroll {
let max_scroll_y = if self.enabled.y { let max_scroll_y = if self.enabled.y {
-self.vertical_bar.amount_hidden -self.vertical_bar.amount_hidden
} else { } else {
Px(0) Px::ZERO
}; };
let new_max_scroll = Point::new(max_scroll_x, max_scroll_y); let new_max_scroll = Point::new(max_scroll_x, max_scroll_y);
if current_max_scroll != new_max_scroll { if current_max_scroll != new_max_scroll {

View file

@ -53,9 +53,9 @@ impl<T> Slider<T> {
value: value.into_dynamic(), value: value.into_dynamic(),
minimum: min.into_value(), minimum: min.into_value(),
maximum: max.into_value(), maximum: max.into_value(),
knob_size: UPx(0), knob_size: UPx::ZERO,
horizontal: true, horizontal: true,
rendered_size: Px(0), rendered_size: Px::ZERO,
} }
} }
@ -135,7 +135,7 @@ where
} else { } else {
position.y position.y
}; };
let position = position.clamp(Px(0), self.rendered_size); let position = position.clamp(Px::ZERO, self.rendered_size);
let percent = position.into_float() / self.rendered_size.into_float(); let percent = position.into_float() / self.rendered_size.into_float();
let min = self.minimum.get(); let min = self.minimum.get();
let max = self.maximum.get(); let max = self.maximum.get();

View file

@ -172,7 +172,7 @@ impl Widget for Stack {
Rect::new( Rect::new(
self.layout self.layout
.orientation .orientation
.make_point(layout.offset, UPx(0)) .make_point(layout.offset, UPx::ZERO)
.into_signed(), .into_signed(),
self.layout self.layout
.orientation .orientation
@ -313,9 +313,9 @@ impl Layout {
orientation, orientation,
children: OrderedLots::new(), children: OrderedLots::new(),
layouts: Vec::new(), layouts: Vec::new(),
other: UPx(0), other: UPx::ZERO,
total_weights: 0, total_weights: 0,
allocated_space: (UPx(0), Lp(0)), allocated_space: (UPx::ZERO, Lp::ZERO),
fractional: Vec::new(), fractional: Vec::new(),
fit_to_content: Vec::new(), fit_to_content: Vec::new(),
premeasured: Vec::new(), premeasured: Vec::new(),
@ -370,12 +370,12 @@ impl Layout {
let layout = match child { let layout = match child {
StackDimension::FitContent => { StackDimension::FitContent => {
self.fit_to_content.push(id); self.fit_to_content.push(id);
UPx(0) UPx::ZERO
} }
StackDimension::Fractional { weight } => { StackDimension::Fractional { weight } => {
self.total_weights += u32::from(weight); self.total_weights += u32::from(weight);
self.fractional.push((id, weight)); self.fractional.push((id, weight));
UPx(0) UPx::ZERO
} }
StackDimension::Measured { min, .. } => { StackDimension::Measured { min, .. } => {
self.premeasured.push(id); self.premeasured.push(id);
@ -389,7 +389,7 @@ impl Layout {
self.layouts.insert( self.layouts.insert(
index, index,
StackLayout { StackLayout {
offset: UPx(0), offset: UPx::ZERO,
size: layout, size: layout,
}, },
); );
@ -412,7 +412,7 @@ impl Layout {
let needs_final_layout = !matches!(other_constraint, ConstraintLimit::Fill(_)); let needs_final_layout = !matches!(other_constraint, ConstraintLimit::Fill(_));
// Measure the children that fit their content // Measure the children that fit their content
self.other = UPx(0); self.other = UPx::ZERO;
for &id in &self.fit_to_content { for &id in &self.fit_to_content {
let index = self.children.index_of_id(id).expect("child not found"); let index = self.children.index_of_id(id).expect("child not found");
let (measured, other) = self.orientation.split_size(measure( let (measured, other) = self.orientation.split_size(measure(
@ -484,7 +484,7 @@ impl Layout {
}; };
// Finally, compute the offsets of all of the widgets. // Finally, compute the offsets of all of the widgets.
let mut offset = UPx(0); let mut offset = UPx::ZERO;
for index in 0..self.children.len() { for index in 0..self.children.len() {
self.layouts[index].offset = offset; self.layouts[index].offset = offset;
offset += self.layouts[index].size; offset += self.layouts[index].size;
@ -593,7 +593,7 @@ mod tests {
orientation.make_size(measured, other) orientation.make_size(measured, other)
}); });
assert_eq!(computed_size, expected_size); assert_eq!(computed_size, expected_size);
let mut offset = UPx(0); let mut offset = UPx::ZERO;
for ((index, &child), &expected) in flex.iter().enumerate().zip(expected) { for ((index, &child), &expected) in flex.iter().enumerate().zip(expected) {
assert_eq!( assert_eq!(
child.size, child.size,
@ -635,11 +635,11 @@ mod tests {
fn size_to_fit() { fn size_to_fit() {
assert_measured_children( assert_measured_children(
&[Child::new(3, 1), Child::new(3, 1), Child::new(3, 1)], &[Child::new(3, 1), Child::new(3, 1), Child::new(3, 1)],
ConstraintLimit::SizeToFit(UPx(10)), ConstraintLimit::SizeToFit(UPx::new(10)),
ConstraintLimit::SizeToFit(UPx(10)), ConstraintLimit::SizeToFit(UPx::new(10)),
&[UPx(3), UPx(3), UPx(3)], &[UPx::new(3), UPx::new(3), UPx::new(3)],
UPx(9), UPx::new(9),
UPx(1), UPx::new(1),
); );
} }
@ -660,11 +660,11 @@ mod tests {
Child::new(3, 1).weighted(1), Child::new(3, 1).weighted(1),
Child::new(3, 1).weighted(1), Child::new(3, 1).weighted(1),
], ],
ConstraintLimit::Fill(UPx(10)), ConstraintLimit::Fill(UPx::new(10)),
ConstraintLimit::SizeToFit(UPx(10)), ConstraintLimit::SizeToFit(UPx::new(10)),
&[UPx(4), UPx(3), UPx(3)], &[UPx::new(4), UPx::new(3), UPx::new(3)],
UPx(10), UPx::new(10),
UPx(7), // 20 / 3 = 6.666, rounded up is 7 UPx::new(7), // 20 / 3 = 6.666, rounded up is 7
); );
// Same as above, but with an 11px box. This creates a leftover of 3 px // Same as above, but with an 11px box. This creates a leftover of 3 px
// (11 % 4), adding 1px to all three children. // (11 % 4), adding 1px to all three children.
@ -674,11 +674,11 @@ mod tests {
Child::new(3, 1).weighted(1), Child::new(3, 1).weighted(1),
Child::new(3, 1).weighted(1), Child::new(3, 1).weighted(1),
], ],
ConstraintLimit::Fill(UPx(11)), ConstraintLimit::Fill(UPx::new(11)),
ConstraintLimit::SizeToFit(UPx(11)), ConstraintLimit::SizeToFit(UPx::new(11)),
&[UPx(5), UPx(3), UPx(3)], &[UPx::new(5), UPx::new(3), UPx::new(3)],
UPx(11), UPx::new(11),
UPx(7), // 20 / 3 = 6.666, rounded up is 7 UPx::new(7), // 20 / 3 = 6.666, rounded up is 7
); );
// 12px box. This creates no leftover. // 12px box. This creates no leftover.
assert_measured_children( assert_measured_children(
@ -687,11 +687,11 @@ mod tests {
Child::new(3, 1).weighted(1), Child::new(3, 1).weighted(1),
Child::new(3, 1).weighted(1), Child::new(3, 1).weighted(1),
], ],
ConstraintLimit::Fill(UPx(12)), ConstraintLimit::Fill(UPx::new(12)),
ConstraintLimit::SizeToFit(UPx(12)), ConstraintLimit::SizeToFit(UPx::new(12)),
&[UPx(6), UPx(3), UPx(3)], &[UPx::new(6), UPx::new(3), UPx::new(3)],
UPx(12), UPx::new(12),
UPx(4), // 20 / 6 = 3.666, rounded up is 4 UPx::new(4), // 20 / 6 = 3.666, rounded up is 4
); );
// 13px box. This creates a leftover of 1 px (13 % 4), adding 1px only // 13px box. This creates a leftover of 1 px (13 % 4), adding 1px only
// to the final child // to the final child
@ -701,11 +701,11 @@ mod tests {
Child::new(3, 1).weighted(1), Child::new(3, 1).weighted(1),
Child::new(3, 1).weighted(1), Child::new(3, 1).weighted(1),
], ],
ConstraintLimit::Fill(UPx(13)), ConstraintLimit::Fill(UPx::new(13)),
ConstraintLimit::SizeToFit(UPx(13)), ConstraintLimit::SizeToFit(UPx::new(13)),
&[UPx(6), UPx(3), UPx(4)], &[UPx::new(6), UPx::new(3), UPx::new(4)],
UPx(13), UPx::new(13),
UPx(4), // 20 / 6 = 3.666, rounded up is 4 UPx::new(4), // 20 / 6 = 3.666, rounded up is 4
); );
} }
@ -713,15 +713,15 @@ mod tests {
fn fixed_size() { fn fixed_size() {
assert_measured_children( assert_measured_children(
&[ &[
Child::new(3, 1).fixed_size(UPx(7)), Child::new(3, 1).fixed_size(UPx::new(7)),
Child::new(3, 1).weighted(1), Child::new(3, 1).weighted(1),
Child::new(3, 1).weighted(1), Child::new(3, 1).weighted(1),
], ],
ConstraintLimit::Fill(UPx(15)), ConstraintLimit::Fill(UPx::new(15)),
ConstraintLimit::SizeToFit(UPx(15)), ConstraintLimit::SizeToFit(UPx::new(15)),
&[UPx(7), UPx(4), UPx(4)], &[UPx::new(7), UPx::new(4), UPx::new(4)],
UPx(15), UPx::new(15),
UPx(1), UPx::new(1),
); );
} }
} }

View file

@ -396,7 +396,7 @@ where
let min_width = resize let min_width = resize
.width .width
.minimum() .minimum()
.map_or(Px(0), |width| width.into_px(graphics.scale())); .map_or(Px::ZERO, |width| width.into_px(graphics.scale()));
let max_width = resize let max_width = resize
.width .width
.maximum() .maximum()
@ -404,7 +404,7 @@ where
let min_height = resize let min_height = resize
.height .height
.minimum() .minimum()
.map_or(Px(0), |height| height.into_px(graphics.scale())); .map_or(Px::ZERO, |height| height.into_px(graphics.scale()));
let max_height = resize let max_height = resize
.height .height
.maximum() .maximum()
@ -566,15 +566,9 @@ where
} }
let actual_size = layout_context.layout(if is_expanded { let actual_size = layout_context.layout(if is_expanded {
Size::new( window_size.map(ConstraintLimit::Fill)
ConstraintLimit::Fill(window_size.width),
ConstraintLimit::Fill(window_size.height),
)
} else { } else {
Size::new( window_size.map(ConstraintLimit::SizeToFit)
ConstraintLimit::SizeToFit(window_size.width),
ConstraintLimit::SizeToFit(window_size.height),
)
}); });
let render_size = actual_size.min(window_size); let render_size = actual_size.min(window_size);
if actual_size != window_size && !resizable { if actual_size != window_size && !resizable {