mirror of
https://github.com/danbulant/cushy
synced 2026-06-18 14:01:10 +00:00
Drawing refactor from Kludgine
This commit is contained in:
parent
01d45a836f
commit
8ae315e229
19 changed files with 229 additions and 240 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -588,9 +588,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "etagere"
|
||||
version = "0.2.8"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf22f748754352918e082e0039335ee92454a5d62bcaf69b5e8daf5907d9644"
|
||||
checksum = "306960881d6c46bd0dd6b7f07442a441418c08d0d3e63d8d080b0f64c6343e4e"
|
||||
dependencies = [
|
||||
"euclid",
|
||||
"svg_fmt",
|
||||
|
|
@ -1089,7 +1089,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
|||
[[package]]
|
||||
name = "kludgine"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/khonsulabs/kludgine#220df4ed07fcf86459a34591a7923d7ddb25e67e"
|
||||
source = "git+https://github.com/khonsulabs/kludgine#74db2e6be9c79f384eafbe57af8fa92de8b8d03c"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"alot",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use kludgine::figures::units::Px;
|
|||
use kludgine::figures::{Angle, IntoSigned, Point, Rect, Size};
|
||||
use kludgine::shapes::Shape;
|
||||
use kludgine::text::{Text, TextOrigin};
|
||||
use kludgine::Color;
|
||||
use kludgine::{Color, DrawableExt};
|
||||
|
||||
fn main() -> gooey::Result<()> {
|
||||
let mut angle = Angle::degrees(0);
|
||||
|
|
@ -14,19 +14,16 @@ fn main() -> gooey::Result<()> {
|
|||
let center = Point::from(context.gfx.size()).into_signed() / 2;
|
||||
context.gfx.draw_text(
|
||||
Text::new("Canvas exposes the full power of Kludgine", Color::WHITE)
|
||||
.origin(TextOrigin::Center),
|
||||
center - Point::new(Px(0), Px(100)),
|
||||
None,
|
||||
None,
|
||||
.origin(TextOrigin::Center)
|
||||
.translate_by(center - Point::new(Px(0), Px(100))),
|
||||
);
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Shape::filled_rect(
|
||||
Rect::new(Point::new(Px(-50), Px(-50)), Size::new(Px(100), Px(100))),
|
||||
Color::RED,
|
||||
),
|
||||
center,
|
||||
Some(angle),
|
||||
None,
|
||||
)
|
||||
.translate_by(center)
|
||||
.rotate_by(angle),
|
||||
)
|
||||
})
|
||||
.tick(Tick::redraws_per_second(60))
|
||||
|
|
|
|||
|
|
@ -15,16 +15,18 @@ fn main() -> gooey::Result {
|
|||
(&username, &password).map_each(|(username, password)| validate(username, password));
|
||||
|
||||
// TODO this should be a grid layout to ensure proper visual alignment.
|
||||
let username_row = "Username"
|
||||
.and(username.clone().into_input().expand())
|
||||
.into_columns();
|
||||
let username_field = "Username"
|
||||
.align_left()
|
||||
.and(username.clone().into_input())
|
||||
.into_rows();
|
||||
|
||||
let password_row = "Password"
|
||||
let password_field = "Password"
|
||||
.align_left()
|
||||
.and(
|
||||
// TODO secure input
|
||||
password.clone().into_input().expand(),
|
||||
password.clone().into_input(),
|
||||
)
|
||||
.into_columns();
|
||||
.into_rows();
|
||||
|
||||
let buttons = "Cancel"
|
||||
.into_button()
|
||||
|
|
@ -46,13 +48,14 @@ fn main() -> gooey::Result {
|
|||
)
|
||||
.into_columns();
|
||||
|
||||
username_row
|
||||
username_field
|
||||
.pad()
|
||||
.and(password_row.pad())
|
||||
.and(password_field.pad())
|
||||
.and(buttons.pad())
|
||||
.into_rows()
|
||||
.contain()
|
||||
.width(Lp::points(300)..Lp::points(600))
|
||||
.width(Lp::inches(3)..Lp::inches(6))
|
||||
.pad()
|
||||
.scroll()
|
||||
.centered()
|
||||
.expand()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use gooey::kludgine::Color;
|
|||
use gooey::value::Dynamic;
|
||||
use gooey::widgets::TileMap;
|
||||
use gooey::{Run, Tick};
|
||||
use kludgine::DrawableExt;
|
||||
|
||||
const PLAYER_SIZE: Px = Px(16);
|
||||
|
||||
|
|
@ -83,16 +84,14 @@ impl Object for Player {
|
|||
fn render(&self, center: Point<Px>, zoom: f32, context: &mut Renderer<'_, '_>) {
|
||||
let zoomed_size = PLAYER_SIZE * zoom;
|
||||
context.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Shape::filled_rect(
|
||||
Rect::new(
|
||||
Point::new(-zoomed_size / 2, -zoomed_size / 2),
|
||||
Size::squared(zoomed_size),
|
||||
),
|
||||
self.color,
|
||||
),
|
||||
center,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.translate_by(center),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
60
examples/todo.rs
Normal file
60
examples/todo.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use gooey::value::Dynamic;
|
||||
use gooey::widget::{Children, MakeWidget, WidgetInstance};
|
||||
use gooey::widgets::input::InputValue;
|
||||
use gooey::widgets::{Button, Label, Scroll, Stack};
|
||||
use gooey::Run;
|
||||
|
||||
fn main() -> gooey::Result {
|
||||
let tasks = Dynamic::default();
|
||||
let children = Dynamic::default();
|
||||
tasks.for_each(children.with_clone(|children| {
|
||||
move |tasks: &Vec<Task>| {
|
||||
update_task_widgets(tasks, &mut children.lock());
|
||||
}
|
||||
}));
|
||||
|
||||
let task_text = Dynamic::default();
|
||||
let valid = task_text.map_each(|text: &String| !text.is_empty());
|
||||
let task_form = Stack::columns(
|
||||
task_text.clone().into_input().expand().and(
|
||||
Button::new("Add Task")
|
||||
.on_click(move |_| {
|
||||
tasks.lock().push(Task::new(task_text.take()));
|
||||
})
|
||||
.into_default()
|
||||
.with_enabled(valid),
|
||||
),
|
||||
);
|
||||
let tasks = Scroll::vertical(Stack::rows(children));
|
||||
|
||||
Stack::rows(task_form.and(tasks)).expand().run()
|
||||
}
|
||||
|
||||
struct Task {
|
||||
widget: WidgetInstance,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub fn new(text: String) -> Self {
|
||||
let widget = Label::new(Dynamic::new(text)).align_left().make_widget();
|
||||
|
||||
Self { widget }
|
||||
}
|
||||
}
|
||||
|
||||
fn update_task_widgets(tasks: &[Task], children: &mut Children) {
|
||||
'tasks: for (index, task) in tasks.iter().enumerate() {
|
||||
for child in index..children.len() {
|
||||
if children[child].id() == task.widget.id() {
|
||||
if child != index {
|
||||
children.swap(child, index);
|
||||
}
|
||||
continue 'tasks;
|
||||
}
|
||||
}
|
||||
|
||||
children.insert(index, task.widget.clone());
|
||||
}
|
||||
|
||||
children.truncate(tasks.len());
|
||||
}
|
||||
|
|
@ -527,8 +527,7 @@ impl<'context, 'window, 'clip, 'gfx, 'pass> GraphicsContext<'context, 'window, '
|
|||
let visible_rect = Rect::from(self.gfx.region().size - (Px(1), Px(1)));
|
||||
let focus_ring =
|
||||
Shape::stroked_rect(visible_rect, color, options.into_px(self.gfx.scale()));
|
||||
self.gfx
|
||||
.draw_shape(&focus_ring, Point::default(), None, None);
|
||||
self.gfx.draw_shape(&focus_ring);
|
||||
}
|
||||
|
||||
/// Renders the default focus ring for this widget.
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@ use std::ops::{Deref, DerefMut};
|
|||
|
||||
use kludgine::figures::units::{Px, UPx};
|
||||
use kludgine::figures::{
|
||||
self, Angle, Fraction, IntoSigned, IntoUnsigned, IsZero, Point, Rect, ScreenScale, ScreenUnit,
|
||||
Size,
|
||||
self, Fraction, IntoSigned, IntoUnsigned, IsZero, Point, Rect, ScreenScale, ScreenUnit, Size,
|
||||
};
|
||||
use kludgine::render::Renderer;
|
||||
use kludgine::shapes::Shape;
|
||||
use kludgine::text::{MeasuredText, Text, TextOrigin};
|
||||
use kludgine::{
|
||||
cosmic_text, ClipGuard, Color, Kludgine, ShaderScalable, ShapeSource, TextureSource,
|
||||
cosmic_text, ClipGuard, Color, Drawable, Kludgine, ShaderScalable, ShapeSource, TextureSource,
|
||||
};
|
||||
|
||||
/// A 2d graphics context
|
||||
|
|
@ -141,28 +140,18 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
|
|||
pub fn fill(&mut self, color: Color) {
|
||||
if color.alpha() > 0 {
|
||||
let rect = Rect::from(self.region.size);
|
||||
self.draw_shape(
|
||||
&Shape::filled_rect(rect, color),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
self.draw_shape(&Shape::filled_rect(rect, color));
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a shape at the origin, rotating and scaling as needed.
|
||||
pub fn draw_shape<Unit>(
|
||||
&mut self,
|
||||
shape: &Shape<Unit, false>,
|
||||
origin: Point<Unit>,
|
||||
rotation_rads: Option<Angle>,
|
||||
scale: Option<f32>,
|
||||
) where
|
||||
pub fn draw_shape<'a, Unit>(&mut self, shape: impl Into<Drawable<&'a Shape<Unit, false>, Unit>>)
|
||||
where
|
||||
Unit: IsZero + ShaderScalable + figures::ScreenUnit + Copy,
|
||||
{
|
||||
let translate = origin + Point::<Unit>::from_px(self.translation(), self.scale());
|
||||
self.renderer
|
||||
.draw_shape(shape, translate, rotation_rads, scale);
|
||||
let mut shape = shape.into();
|
||||
shape.translation += Point::<Unit>::from_px(self.translation(), self.scale());
|
||||
self.renderer.draw_shape(shape);
|
||||
}
|
||||
|
||||
/// Draws `texture` at `destination`, scaling as necessary.
|
||||
|
|
@ -177,20 +166,18 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
|
|||
|
||||
/// Draws a shape that was created with texture coordinates, applying the
|
||||
/// provided texture.
|
||||
pub fn draw_textured_shape<Unit>(
|
||||
pub fn draw_textured_shape<'shape, Unit, Shape>(
|
||||
&mut self,
|
||||
shape: &impl ShapeSource<Unit, true>,
|
||||
shape: impl Into<Drawable<&'shape Shape, Unit>>,
|
||||
texture: &impl TextureSource,
|
||||
origin: Point<Unit>,
|
||||
rotation: Option<Angle>,
|
||||
scale: Option<f32>,
|
||||
) where
|
||||
Unit: IsZero + ShaderScalable + figures::ScreenUnit + Copy,
|
||||
i32: From<<Unit as IntoSigned>::Signed>,
|
||||
Shape: ShapeSource<Unit, true> + 'shape,
|
||||
{
|
||||
let translate = origin + Point::<Unit>::from_px(self.translation(), self.scale());
|
||||
self.renderer
|
||||
.draw_textured_shape(shape, texture, translate, rotation, scale);
|
||||
let mut shape = shape.into();
|
||||
shape.translation += Point::<Unit>::from_px(self.translation(), self.scale());
|
||||
self.renderer.draw_textured_shape(shape, texture);
|
||||
}
|
||||
|
||||
/// Measures `text` using the current text settings.
|
||||
|
|
@ -204,17 +191,13 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
|
|||
}
|
||||
|
||||
/// Draws `text` using the current text settings.
|
||||
pub fn draw_text<'a, Unit>(
|
||||
&mut self,
|
||||
text: impl Into<Text<'a, Unit>>,
|
||||
translate: Point<Unit>,
|
||||
rotation: Option<Angle>,
|
||||
scale: Option<f32>,
|
||||
) where
|
||||
pub fn draw_text<'a, Unit>(&mut self, text: impl Into<Drawable<Text<'a, Unit>, Unit>>)
|
||||
where
|
||||
Unit: ScreenUnit,
|
||||
{
|
||||
let translate = translate + Point::<Unit>::from_px(self.translation(), self.scale());
|
||||
self.renderer.draw_text(text, translate, rotation, scale);
|
||||
let mut text = text.into();
|
||||
text.translation += Point::<Unit>::from_px(self.translation(), self.scale());
|
||||
self.renderer.draw_text(text);
|
||||
}
|
||||
|
||||
/// Prepares the text layout contained in `buffer` to be rendered.
|
||||
|
|
@ -224,20 +207,18 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
|
|||
///
|
||||
/// `origin` allows controlling how the text will be drawn relative to the
|
||||
/// coordinate provided in [`render()`](kludgine::PreparedGraphic::render).
|
||||
pub fn draw_text_buffer<Unit>(
|
||||
pub fn draw_text_buffer<'a, Unit>(
|
||||
&mut self,
|
||||
buffer: &cosmic_text::Buffer,
|
||||
buffer: impl Into<Drawable<&'a cosmic_text::Buffer, Unit>>,
|
||||
default_color: Color,
|
||||
origin: TextOrigin<Px>,
|
||||
translate: Point<Unit>,
|
||||
rotation: Option<Angle>,
|
||||
scale: Option<f32>,
|
||||
) where
|
||||
Unit: ScreenUnit,
|
||||
{
|
||||
let translate = translate + Point::<Unit>::from_px(self.translation(), self.scale());
|
||||
let mut buffer = buffer.into();
|
||||
buffer.translation += Point::<Unit>::from_px(self.translation(), self.scale());
|
||||
self.renderer
|
||||
.draw_text_buffer(buffer, default_color, origin, translate, rotation, scale);
|
||||
.draw_text_buffer(buffer, default_color, origin);
|
||||
}
|
||||
|
||||
/// Measures `buffer` and caches the results using `default_color` when
|
||||
|
|
@ -260,19 +241,16 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> {
|
|||
///
|
||||
/// `origin` allows controlling how the text will be drawn relative to the
|
||||
/// coordinate provided in [`render()`](kludgine::PreparedGraphic::render).
|
||||
pub fn draw_measured_text<Unit>(
|
||||
pub fn draw_measured_text<'a, Unit>(
|
||||
&mut self,
|
||||
text: &MeasuredText<Unit>,
|
||||
text: impl Into<Drawable<&'a MeasuredText<Unit>, Unit>>,
|
||||
origin: TextOrigin<Unit>,
|
||||
translate: Point<Unit>,
|
||||
rotation: Option<Angle>,
|
||||
scale: Option<f32>,
|
||||
) where
|
||||
Unit: ScreenUnit,
|
||||
{
|
||||
let translate = translate + Point::<Unit>::from_px(self.translation(), self.scale());
|
||||
self.renderer
|
||||
.draw_measured_text(text, origin, translate, rotation, scale);
|
||||
let mut text = text.into();
|
||||
text.translation += Point::<Unit>::from_px(self.translation(), self.scale());
|
||||
self.renderer.draw_measured_text(text, origin);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
16
src/lib.rs
16
src/lib.rs
|
|
@ -36,9 +36,9 @@ pub use self::tick::{InputState, Tick};
|
|||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum ConstraintLimit {
|
||||
/// The widget is expected to occupy a known size.
|
||||
Known(UPx),
|
||||
Fill(UPx),
|
||||
/// The widget is expected to resize itself to fit within the size provided.
|
||||
ClippedAfter(UPx),
|
||||
SizeToFit(UPx),
|
||||
}
|
||||
|
||||
impl ConstraintLimit {
|
||||
|
|
@ -46,7 +46,7 @@ impl ConstraintLimit {
|
|||
#[must_use]
|
||||
pub fn max(self) -> UPx {
|
||||
match self {
|
||||
ConstraintLimit::Known(v) | ConstraintLimit::ClippedAfter(v) => v,
|
||||
ConstraintLimit::Fill(v) | ConstraintLimit::SizeToFit(v) => v,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,8 +62,8 @@ impl ConstraintLimit {
|
|||
{
|
||||
let measured = measured.into_upx(scale);
|
||||
match self {
|
||||
ConstraintLimit::Known(size) => size.max(measured),
|
||||
ConstraintLimit::ClippedAfter(_) => measured,
|
||||
ConstraintLimit::Fill(size) => size.max(measured),
|
||||
ConstraintLimit::SizeToFit(_) => measured,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,10 +73,8 @@ impl Sub<UPx> for ConstraintLimit {
|
|||
|
||||
fn sub(self, rhs: UPx) -> Self::Output {
|
||||
match self {
|
||||
ConstraintLimit::Known(px) => ConstraintLimit::Known(px.saturating_sub(rhs)),
|
||||
ConstraintLimit::ClippedAfter(px) => {
|
||||
ConstraintLimit::ClippedAfter(px.saturating_sub(rhs))
|
||||
}
|
||||
ConstraintLimit::Fill(px) => ConstraintLimit::Fill(px.saturating_sub(rhs)),
|
||||
ConstraintLimit::SizeToFit(px) => ConstraintLimit::SizeToFit(px.saturating_sub(rhs)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,15 +141,15 @@ impl FrameInfo {
|
|||
// into ClippedAfter mode to make the widget attempt to size the
|
||||
// content to fit.
|
||||
(Some(one), None) | (None, Some(one)) => {
|
||||
ConstraintLimit::ClippedAfter(available.max() - one)
|
||||
ConstraintLimit::SizeToFit(available.max() - one)
|
||||
}
|
||||
(None, None) => ConstraintLimit::ClippedAfter(available.max()),
|
||||
(None, None) => ConstraintLimit::SizeToFit(available.max()),
|
||||
}
|
||||
}
|
||||
|
||||
fn measure(&self, available: ConstraintLimit, content: UPx) -> (UPx, UPx, UPx) {
|
||||
match available {
|
||||
ConstraintLimit::Known(size) => {
|
||||
ConstraintLimit::Fill(size) => {
|
||||
let remaining = size.saturating_sub(content);
|
||||
let (a, b) = match (self.a, self.b) {
|
||||
(Some(a), Some(b)) => (a, b),
|
||||
|
|
@ -164,7 +164,7 @@ impl FrameInfo {
|
|||
|
||||
(a, b, size - a - b)
|
||||
}
|
||||
ConstraintLimit::ClippedAfter(_) => (
|
||||
ConstraintLimit::SizeToFit(_) => (
|
||||
self.a.unwrap_or_default(),
|
||||
self.b.unwrap_or_default(),
|
||||
content,
|
||||
|
|
|
|||
|
|
@ -369,9 +369,7 @@ impl Widget for Button {
|
|||
color,
|
||||
two_lp_stroke,
|
||||
);
|
||||
context
|
||||
.gfx
|
||||
.draw_shape(&focus_ring, Point::default(), None, None);
|
||||
context.gfx.draw_shape(&focus_ring);
|
||||
} else if context.is_default() {
|
||||
context.stroke_outline(context.get(&OutlineColor), two_lp_stroke);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -178,12 +178,9 @@ impl WrapperWidget for CheckboxLabel {
|
|||
match self.value.get_tracking_refresh(context) {
|
||||
state @ (CheckboxState::Checked | CheckboxState::Indeterminant) => {
|
||||
let color = context.get(&WidgetAccentColor);
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(checkbox_rect, color),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
context
|
||||
.gfx
|
||||
.draw_shape(&Shape::filled_rect(checkbox_rect, color));
|
||||
let icon_area = checkbox_rect.inset(Lp::points(3).into_px(context.gfx.scale()));
|
||||
let text_color = context.get(&TextColor);
|
||||
let center = icon_area.origin + icon_area.size / 2;
|
||||
|
|
@ -200,9 +197,6 @@ impl WrapperWidget for CheckboxLabel {
|
|||
))
|
||||
.build()
|
||||
.stroke(text_color, stroke_options),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
context.gfx.draw_shape(
|
||||
|
|
@ -213,20 +207,14 @@ impl WrapperWidget for CheckboxLabel {
|
|||
))
|
||||
.build()
|
||||
.stroke(text_color, stroke_options),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
CheckboxState::Unchecked => {
|
||||
let color = context.get(&OutlineColor);
|
||||
context.gfx.draw_shape(
|
||||
&Shape::stroked_rect(checkbox_rect, color, stroke_options),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
context
|
||||
.gfx
|
||||
.draw_shape(&Shape::stroked_rect(checkbox_rect, color, stroke_options));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ impl WrapperWidget for Expand {
|
|||
context: &mut LayoutContext<'_, '_, '_, '_, '_>,
|
||||
) -> WrappedLayout {
|
||||
let available_space = Size::new(
|
||||
ConstraintLimit::Known(available_space.width.max()),
|
||||
ConstraintLimit::Known(available_space.height.max()),
|
||||
ConstraintLimit::Fill(available_space.width.max()),
|
||||
ConstraintLimit::Fill(available_space.height.max()),
|
||||
);
|
||||
let child = self.child.mounted(&mut context.as_event_context());
|
||||
let size = context.for_other(&child).layout(available_space);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use kludgine::figures::{
|
|||
};
|
||||
use kludgine::shapes::{Shape, StrokeOptions};
|
||||
use kludgine::text::{MeasuredText, Text, TextOrigin};
|
||||
use kludgine::Color;
|
||||
use kludgine::{Color, DrawableExt};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
|
|
@ -849,55 +849,47 @@ where
|
|||
// Single line selection
|
||||
let width = end_position.x - start_position.x;
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Shape::filled_rect(
|
||||
Rect::new(start_position, Size::new(width, cache.measured.line_height)),
|
||||
highlight,
|
||||
),
|
||||
padding,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.translate_by(padding),
|
||||
);
|
||||
} else {
|
||||
// Draw from start to end of line,
|
||||
let width = size.width.into_signed() - start_position.x;
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Shape::filled_rect(
|
||||
Rect::new(start_position, Size::new(width, cache.measured.line_height)),
|
||||
highlight,
|
||||
),
|
||||
padding,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.translate_by(padding),
|
||||
);
|
||||
// Fill region between
|
||||
let bottom_of_first_line = start_position.y + cache.measured.line_height;
|
||||
let distance_between = end_position.y - bottom_of_first_line;
|
||||
if distance_between > 0 {
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Shape::filled_rect(
|
||||
Rect::new(
|
||||
Point::new(Px(0), bottom_of_first_line),
|
||||
Size::new(size.width.into_signed(), distance_between),
|
||||
),
|
||||
highlight,
|
||||
),
|
||||
padding,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.translate_by(padding),
|
||||
);
|
||||
}
|
||||
// Draw from 0 to end + width
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Shape::filled_rect(
|
||||
Rect::new(
|
||||
Point::new(Px(0), end_position.y),
|
||||
Size::new(end_position.x + end_width, cache.measured.line_height),
|
||||
),
|
||||
highlight,
|
||||
),
|
||||
padding,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.translate_by(padding),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -906,16 +898,14 @@ where
|
|||
if window_focused && cursor_state.visible {
|
||||
let cursor_width = Lp::points(2).into_px(context.gfx.scale());
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Shape::filled_rect(
|
||||
Rect::new(
|
||||
Point::new(location.x - cursor_width / 2, location.y),
|
||||
Size::new(cursor_width, cache.measured.line_height),
|
||||
),
|
||||
highlight, // TODO cursor should be a bold color, highlight probably not. This should have its own color.
|
||||
),
|
||||
padding,
|
||||
None,
|
||||
None,
|
||||
highlight,
|
||||
)
|
||||
.translate_by(padding),
|
||||
);
|
||||
}
|
||||
if window_focused {
|
||||
|
|
@ -931,7 +921,7 @@ where
|
|||
|
||||
context
|
||||
.gfx
|
||||
.draw_measured_text(cache.measured, TextOrigin::TopLeft, padding, None, None);
|
||||
.draw_measured_text(cache.measured.translate_by(padding), TextOrigin::TopLeft);
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use kludgine::figures::units::{Px, UPx};
|
||||
use kludgine::figures::{Point, ScreenScale, Size};
|
||||
use kludgine::text::{MeasuredText, Text, TextOrigin};
|
||||
use kludgine::Color;
|
||||
use kludgine::{Color, DrawableExt};
|
||||
|
||||
use crate::context::{GraphicsContext, LayoutContext};
|
||||
use crate::styles::components::{IntrinsicPadding, TextColor};
|
||||
|
|
@ -71,7 +71,7 @@ impl Widget for Label {
|
|||
|
||||
context
|
||||
.gfx
|
||||
.draw_measured_text(prepared_text, TextOrigin::Center, center, None, None);
|
||||
.draw_measured_text(prepared_text.translate_by(center), TextOrigin::Center);
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
|
|||
|
|
@ -124,10 +124,10 @@ fn override_constraint(
|
|||
scale: Fraction,
|
||||
) -> ConstraintLimit {
|
||||
match constraint {
|
||||
ConstraintLimit::Known(size) => ConstraintLimit::Known(range.clamp(size, scale)),
|
||||
ConstraintLimit::ClippedAfter(clipped_after) => match (range.minimum(), range.maximum()) {
|
||||
(Some(min), Some(max)) if min == max => ConstraintLimit::Known(min.into_upx(scale)),
|
||||
_ => ConstraintLimit::ClippedAfter(range.clamp(clipped_after, scale)),
|
||||
ConstraintLimit::Fill(size) => ConstraintLimit::Fill(range.clamp(size, scale)),
|
||||
ConstraintLimit::SizeToFit(clipped_after) => match (range.minimum(), range.maximum()) {
|
||||
(Some(min), Some(max)) if min == max => ConstraintLimit::Fill(min.into_upx(scale)),
|
||||
_ => ConstraintLimit::SizeToFit(range.clamp(clipped_after, scale)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,33 +129,23 @@ impl Widget for Scroll {
|
|||
let size = context.gfx.region().size;
|
||||
|
||||
if self.horizontal_bar.amount_hidden > 0 {
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Rect::new(
|
||||
Point::new(self.horizontal_bar.offset, size.height - self.bar_width),
|
||||
Size::new(self.horizontal_bar.size, self.bar_width),
|
||||
),
|
||||
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
|
||||
context.gfx.draw_shape(&Shape::filled_rect(
|
||||
Rect::new(
|
||||
Point::new(self.horizontal_bar.offset, size.height - self.bar_width),
|
||||
Size::new(self.horizontal_bar.size, self.bar_width),
|
||||
),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
|
||||
));
|
||||
}
|
||||
|
||||
if self.vertical_bar.amount_hidden > 0 {
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Rect::new(
|
||||
Point::new(size.width - self.bar_width, self.vertical_bar.offset),
|
||||
Size::new(self.bar_width, self.vertical_bar.size),
|
||||
),
|
||||
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
|
||||
context.gfx.draw_shape(&Shape::filled_rect(
|
||||
Rect::new(
|
||||
Point::new(size.width - self.bar_width, self.vertical_bar.offset),
|
||||
Size::new(self.bar_width, self.vertical_bar.size),
|
||||
),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
Color::new_f32(1.0, 1.0, 1.0, *self.scrollbar_opacity.get()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -175,12 +165,12 @@ impl Widget for Scroll {
|
|||
Size::new(available_space.width.max(), available_space.height.max()).into_signed();
|
||||
let max_extents = Size::new(
|
||||
if self.enabled.x {
|
||||
ConstraintLimit::ClippedAfter((control_size.width).into_unsigned())
|
||||
ConstraintLimit::SizeToFit((control_size.width).into_unsigned())
|
||||
} else {
|
||||
available_space.width
|
||||
},
|
||||
if self.enabled.y {
|
||||
ConstraintLimit::ClippedAfter((control_size.height).into_unsigned())
|
||||
ConstraintLimit::SizeToFit((control_size.height).into_unsigned())
|
||||
} else {
|
||||
available_space.height
|
||||
},
|
||||
|
|
@ -285,8 +275,8 @@ impl Widget for Scroll {
|
|||
fn constrain_child(constraint: ConstraintLimit, measured: Px) -> UPx {
|
||||
let measured = measured.into_unsigned();
|
||||
match constraint {
|
||||
ConstraintLimit::Known(size) => size.min(measured),
|
||||
ConstraintLimit::ClippedAfter(_) => measured,
|
||||
ConstraintLimit::Fill(size) => size.min(measured),
|
||||
ConstraintLimit::SizeToFit(_) => measured,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use kludgine::figures::{
|
|||
Size,
|
||||
};
|
||||
use kludgine::shapes::Shape;
|
||||
use kludgine::{Color, Origin};
|
||||
use kludgine::{Color, DrawableExt, Origin};
|
||||
|
||||
use crate::animation::{LinearInterpolate, PercentBetween};
|
||||
use crate::context::{EventContext, GraphicsContext, LayoutContext};
|
||||
|
|
@ -85,53 +85,42 @@ impl<T> Slider<T> {
|
|||
let half_track = spec.track_size / 2;
|
||||
// Draw the track
|
||||
if value_location > spec.half_knob {
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Rect::new(
|
||||
flipped(
|
||||
!self.horizontal,
|
||||
Point::new(spec.half_knob, spec.half_knob - half_track),
|
||||
),
|
||||
flipped(!self.horizontal, Size::new(value_location, spec.track_size)),
|
||||
context.gfx.draw_shape(&Shape::filled_rect(
|
||||
Rect::new(
|
||||
flipped(
|
||||
!self.horizontal,
|
||||
Point::new(spec.half_knob, spec.half_knob - half_track),
|
||||
),
|
||||
spec.track_color,
|
||||
flipped(!self.horizontal, Size::new(value_location, spec.track_size)),
|
||||
),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
spec.track_color,
|
||||
));
|
||||
}
|
||||
|
||||
if value_location < track_length {
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Rect::new(
|
||||
flipped(
|
||||
!self.horizontal,
|
||||
Point::new(value_location, spec.half_knob - half_track),
|
||||
),
|
||||
flipped(
|
||||
!self.horizontal,
|
||||
Size::new(
|
||||
track_length - value_location + spec.half_knob,
|
||||
spec.track_size,
|
||||
),
|
||||
context.gfx.draw_shape(&Shape::filled_rect(
|
||||
Rect::new(
|
||||
flipped(
|
||||
!self.horizontal,
|
||||
Point::new(value_location, spec.half_knob - half_track),
|
||||
),
|
||||
flipped(
|
||||
!self.horizontal,
|
||||
Size::new(
|
||||
track_length - value_location + spec.half_knob,
|
||||
spec.track_size,
|
||||
),
|
||||
),
|
||||
spec.inactive_track_color,
|
||||
),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
spec.inactive_track_color,
|
||||
));
|
||||
}
|
||||
|
||||
// Draw the knob
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_circle(spec.half_knob, spec.knob_color, Origin::Center),
|
||||
flipped(!self.horizontal, Point::new(value_location, spec.half_knob)),
|
||||
None,
|
||||
None,
|
||||
Shape::filled_circle(spec.half_knob, spec.knob_color, Origin::Center).translate_by(
|
||||
flipped(!self.horizontal, Point::new(value_location, spec.half_knob)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -229,7 +218,7 @@ where
|
|||
.into_upx(context.gfx.scale());
|
||||
|
||||
match (available_space.width, available_space.height) {
|
||||
(ConstraintLimit::Known(width), ConstraintLimit::Known(height)) => {
|
||||
(ConstraintLimit::Fill(width), ConstraintLimit::Fill(height)) => {
|
||||
// This comparison is done such that if width == height, we end
|
||||
// up with a horizontal slider.
|
||||
if width < height {
|
||||
|
|
@ -240,13 +229,13 @@ where
|
|||
Size::new(width.max(minimum_size), self.knob_size)
|
||||
}
|
||||
}
|
||||
(ConstraintLimit::Known(width), ConstraintLimit::ClippedAfter(_)) => {
|
||||
(ConstraintLimit::Fill(width), ConstraintLimit::SizeToFit(_)) => {
|
||||
Size::new(width.max(minimum_size), self.knob_size)
|
||||
}
|
||||
(ConstraintLimit::ClippedAfter(_), ConstraintLimit::Known(height)) => {
|
||||
(ConstraintLimit::SizeToFit(_), ConstraintLimit::Fill(height)) => {
|
||||
Size::new(self.knob_size, height.max(minimum_size))
|
||||
}
|
||||
(ConstraintLimit::ClippedAfter(width), ConstraintLimit::ClippedAfter(_)) => {
|
||||
(ConstraintLimit::SizeToFit(width), ConstraintLimit::SizeToFit(_)) => {
|
||||
// When we have no limit on our, we still want to be draggable.
|
||||
// Since we have no limit in both directions, we have to make a
|
||||
// choice: horizontal or vertical. It seems to @ecton at the
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ impl Layout {
|
|||
// widgets an opportunity to lay themselves out in the full area. This
|
||||
// requires one extra layout call, so we avoid persisting layouts during
|
||||
// the first loop if this is the case.
|
||||
let needs_final_layout = !matches!(other_constraint, ConstraintLimit::Known(_));
|
||||
let needs_final_layout = !matches!(other_constraint, ConstraintLimit::Fill(_));
|
||||
|
||||
// Measure the children that fit their content
|
||||
self.other = UPx(0);
|
||||
|
|
@ -418,7 +418,7 @@ impl Layout {
|
|||
let (measured, other) = self.orientation.split_size(measure(
|
||||
index,
|
||||
self.orientation
|
||||
.make_size(ConstraintLimit::ClippedAfter(remaining), other_constraint),
|
||||
.make_size(ConstraintLimit::SizeToFit(remaining), other_constraint),
|
||||
!needs_final_layout,
|
||||
));
|
||||
self.layouts[index].size = measured;
|
||||
|
|
@ -432,7 +432,7 @@ impl Layout {
|
|||
let (_, other) = self.orientation.split_size(measure(
|
||||
index,
|
||||
self.orientation.make_size(
|
||||
ConstraintLimit::Known(self.layouts[index].size),
|
||||
ConstraintLimit::Fill(self.layouts[index].size),
|
||||
other_constraint,
|
||||
),
|
||||
!needs_final_layout,
|
||||
|
|
@ -469,7 +469,7 @@ impl Layout {
|
|||
let (_, measured) = self.orientation.split_size(measure(
|
||||
index,
|
||||
self.orientation.make_size(
|
||||
ConstraintLimit::Known(self.layouts[index].size.into_upx(scale)),
|
||||
ConstraintLimit::Fill(self.layouts[index].size.into_upx(scale)),
|
||||
other_constraint,
|
||||
),
|
||||
!needs_final_layout,
|
||||
|
|
@ -479,8 +479,8 @@ impl Layout {
|
|||
}
|
||||
|
||||
self.other = match other_constraint {
|
||||
ConstraintLimit::Known(max) => self.other.max(max),
|
||||
ConstraintLimit::ClippedAfter(clip_limit) => self.other.min(clip_limit),
|
||||
ConstraintLimit::Fill(max) => self.other.max(max),
|
||||
ConstraintLimit::SizeToFit(clip_limit) => self.other.min(clip_limit),
|
||||
};
|
||||
|
||||
// Finally, compute the offsets of all of the widgets.
|
||||
|
|
@ -492,8 +492,8 @@ impl Layout {
|
|||
self.orientation.split_size(measure(
|
||||
index,
|
||||
self.orientation.make_size(
|
||||
ConstraintLimit::Known(self.layouts[index].size.into_upx(scale)),
|
||||
ConstraintLimit::Known(self.other),
|
||||
ConstraintLimit::Fill(self.layouts[index].size.into_upx(scale)),
|
||||
ConstraintLimit::Fill(self.other),
|
||||
),
|
||||
true,
|
||||
));
|
||||
|
|
@ -635,8 +635,8 @@ mod tests {
|
|||
fn size_to_fit() {
|
||||
assert_measured_children(
|
||||
&[Child::new(3, 1), Child::new(3, 1), Child::new(3, 1)],
|
||||
ConstraintLimit::ClippedAfter(UPx(10)),
|
||||
ConstraintLimit::ClippedAfter(UPx(10)),
|
||||
ConstraintLimit::SizeToFit(UPx(10)),
|
||||
ConstraintLimit::SizeToFit(UPx(10)),
|
||||
&[UPx(3), UPx(3), UPx(3)],
|
||||
UPx(9),
|
||||
UPx(1),
|
||||
|
|
@ -660,8 +660,8 @@ mod tests {
|
|||
Child::new(3, 1).weighted(1),
|
||||
Child::new(3, 1).weighted(1),
|
||||
],
|
||||
ConstraintLimit::Known(UPx(10)),
|
||||
ConstraintLimit::ClippedAfter(UPx(10)),
|
||||
ConstraintLimit::Fill(UPx(10)),
|
||||
ConstraintLimit::SizeToFit(UPx(10)),
|
||||
&[UPx(4), UPx(3), UPx(3)],
|
||||
UPx(10),
|
||||
UPx(7), // 20 / 3 = 6.666, rounded up is 7
|
||||
|
|
@ -674,8 +674,8 @@ mod tests {
|
|||
Child::new(3, 1).weighted(1),
|
||||
Child::new(3, 1).weighted(1),
|
||||
],
|
||||
ConstraintLimit::Known(UPx(11)),
|
||||
ConstraintLimit::ClippedAfter(UPx(11)),
|
||||
ConstraintLimit::Fill(UPx(11)),
|
||||
ConstraintLimit::SizeToFit(UPx(11)),
|
||||
&[UPx(5), UPx(3), UPx(3)],
|
||||
UPx(11),
|
||||
UPx(7), // 20 / 3 = 6.666, rounded up is 7
|
||||
|
|
@ -687,8 +687,8 @@ mod tests {
|
|||
Child::new(3, 1).weighted(1),
|
||||
Child::new(3, 1).weighted(1),
|
||||
],
|
||||
ConstraintLimit::Known(UPx(12)),
|
||||
ConstraintLimit::ClippedAfter(UPx(12)),
|
||||
ConstraintLimit::Fill(UPx(12)),
|
||||
ConstraintLimit::SizeToFit(UPx(12)),
|
||||
&[UPx(6), UPx(3), UPx(3)],
|
||||
UPx(12),
|
||||
UPx(4), // 20 / 6 = 3.666, rounded up is 4
|
||||
|
|
@ -701,8 +701,8 @@ mod tests {
|
|||
Child::new(3, 1).weighted(1),
|
||||
Child::new(3, 1).weighted(1),
|
||||
],
|
||||
ConstraintLimit::Known(UPx(13)),
|
||||
ConstraintLimit::ClippedAfter(UPx(13)),
|
||||
ConstraintLimit::Fill(UPx(13)),
|
||||
ConstraintLimit::SizeToFit(UPx(13)),
|
||||
&[UPx(6), UPx(3), UPx(4)],
|
||||
UPx(13),
|
||||
UPx(4), // 20 / 6 = 3.666, rounded up is 4
|
||||
|
|
@ -717,8 +717,8 @@ mod tests {
|
|||
Child::new(3, 1).weighted(1),
|
||||
Child::new(3, 1).weighted(1),
|
||||
],
|
||||
ConstraintLimit::Known(UPx(15)),
|
||||
ConstraintLimit::ClippedAfter(UPx(15)),
|
||||
ConstraintLimit::Fill(UPx(15)),
|
||||
ConstraintLimit::SizeToFit(UPx(15)),
|
||||
&[UPx(7), UPx(4), UPx(4)],
|
||||
UPx(15),
|
||||
UPx(1),
|
||||
|
|
|
|||
|
|
@ -567,13 +567,13 @@ where
|
|||
|
||||
let actual_size = layout_context.layout(if is_expanded {
|
||||
Size::new(
|
||||
ConstraintLimit::Known(window_size.width),
|
||||
ConstraintLimit::Known(window_size.height),
|
||||
ConstraintLimit::Fill(window_size.width),
|
||||
ConstraintLimit::Fill(window_size.height),
|
||||
)
|
||||
} else {
|
||||
Size::new(
|
||||
ConstraintLimit::ClippedAfter(window_size.width),
|
||||
ConstraintLimit::ClippedAfter(window_size.height),
|
||||
ConstraintLimit::SizeToFit(window_size.width),
|
||||
ConstraintLimit::SizeToFit(window_size.height),
|
||||
)
|
||||
});
|
||||
let render_size = actual_size.min(window_size);
|
||||
|
|
|
|||
Loading…
Reference in a new issue