new workspace
This commit is contained in:
15
wgui/src/widget/div.rs
Normal file
15
wgui/src/widget/div.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use super::{WidgetObj, WidgetState};
|
||||
|
||||
pub struct Div {}
|
||||
|
||||
impl Div {
|
||||
pub fn create() -> anyhow::Result<WidgetState> {
|
||||
WidgetState::new(Box::new(Self {}))
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetObj for Div {
|
||||
fn draw(&mut self, _state: &mut super::DrawState, _params: &super::DrawParams) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
342
wgui/src/widget/mod.rs
Normal file
342
wgui/src/widget/mod.rs
Normal file
@@ -0,0 +1,342 @@
|
||||
use glam::Vec2;
|
||||
|
||||
use super::drawing::RenderPrimitive;
|
||||
use crate::{
|
||||
animation,
|
||||
any::AnyTrait,
|
||||
drawing,
|
||||
event::{CallbackData, Event, EventListener, MouseWheelEvent},
|
||||
layout::{Layout, WidgetID, WidgetMap},
|
||||
transform_stack::TransformStack,
|
||||
};
|
||||
|
||||
pub mod div;
|
||||
pub mod rectangle;
|
||||
pub mod sprite;
|
||||
pub mod text;
|
||||
pub mod util;
|
||||
|
||||
pub struct WidgetData {
|
||||
pub hovered: bool,
|
||||
pub pressed: bool,
|
||||
pub scrolling: Vec2, // normalized, 0.0-1.0. Not used in case if overflow != scroll
|
||||
pub transform: glam::Mat4,
|
||||
}
|
||||
pub struct WidgetState {
|
||||
pub data: WidgetData,
|
||||
pub obj: Box<dyn WidgetObj>,
|
||||
pub event_listeners: Vec<EventListener>,
|
||||
}
|
||||
|
||||
impl WidgetState {
|
||||
fn new(obj: Box<dyn WidgetObj>) -> anyhow::Result<WidgetState> {
|
||||
Ok(Self {
|
||||
data: WidgetData {
|
||||
hovered: false,
|
||||
pressed: false,
|
||||
scrolling: Vec2::default(),
|
||||
transform: glam::Mat4::IDENTITY,
|
||||
},
|
||||
event_listeners: Vec::new(),
|
||||
obj,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// global draw params
|
||||
pub struct DrawState<'a> {
|
||||
pub layout: &'a Layout,
|
||||
pub primitives: &'a mut Vec<RenderPrimitive>,
|
||||
pub transform_stack: &'a mut TransformStack,
|
||||
pub depth: f32, //TODO: actually use this in shader
|
||||
}
|
||||
|
||||
// per-widget draw params
|
||||
pub struct DrawParams<'a> {
|
||||
pub node_id: taffy::NodeId,
|
||||
pub style: &'a taffy::Style,
|
||||
pub taffy_layout: &'a taffy::Layout,
|
||||
}
|
||||
|
||||
pub trait WidgetObj: AnyTrait {
|
||||
fn draw(&mut self, state: &mut DrawState, params: &DrawParams);
|
||||
fn measure(
|
||||
&mut self,
|
||||
_known_dimensions: taffy::Size<Option<f32>>,
|
||||
_available_space: taffy::Size<taffy::AvailableSpace>,
|
||||
) -> taffy::Size<f32> {
|
||||
taffy::Size::ZERO
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventParams<'a> {
|
||||
pub node_id: taffy::NodeId,
|
||||
pub style: &'a taffy::Style,
|
||||
pub taffy_layout: &'a taffy::Layout,
|
||||
pub widgets: &'a WidgetMap,
|
||||
pub tree: &'a taffy::TaffyTree<WidgetID>,
|
||||
pub transform_stack: &'a TransformStack,
|
||||
pub animations: &'a mut Vec<animation::Animation>,
|
||||
pub needs_redraw: &'a mut bool,
|
||||
pub dirty_nodes: &'a mut Vec<taffy::NodeId>,
|
||||
}
|
||||
|
||||
pub enum EventResult {
|
||||
Pass,
|
||||
Consumed,
|
||||
Outside,
|
||||
}
|
||||
|
||||
fn get_scroll_enabled(style: &taffy::Style) -> (bool, bool) {
|
||||
(
|
||||
style.overflow.x == taffy::Overflow::Scroll,
|
||||
style.overflow.y == taffy::Overflow::Scroll,
|
||||
)
|
||||
}
|
||||
|
||||
pub struct ScrollbarInfo {
|
||||
// total contents size of the currently scrolling widget
|
||||
content_size: Vec2,
|
||||
// 0.0 - 1.0
|
||||
// 1.0: scrollbar handle not visible (inactive)
|
||||
handle_size: Vec2,
|
||||
}
|
||||
|
||||
pub fn get_scrollbar_info(l: &taffy::Layout) -> Option<ScrollbarInfo> {
|
||||
let overflow = Vec2::new(l.scroll_width(), l.scroll_height());
|
||||
if overflow.x == 0.0 && overflow.y == 0.0 {
|
||||
return None; // not overflowing
|
||||
}
|
||||
|
||||
let content_size = Vec2::new(l.content_size.width, l.content_size.height);
|
||||
let handle_size = 1.0 - (overflow / content_size);
|
||||
|
||||
Some(ScrollbarInfo {
|
||||
content_size,
|
||||
handle_size,
|
||||
})
|
||||
}
|
||||
|
||||
impl dyn WidgetObj {
|
||||
pub fn get_as<T: 'static>(&self) -> &T {
|
||||
let any = self.as_any();
|
||||
any.downcast_ref::<T>().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_as_mut<T: 'static>(&mut self) -> &mut T {
|
||||
let any = self.as_any_mut();
|
||||
any.downcast_mut::<T>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetState {
|
||||
pub fn add_event_listener(&mut self, listener: EventListener) {
|
||||
self.event_listeners.push(listener);
|
||||
}
|
||||
|
||||
pub fn get_scroll_shift(&self, info: &ScrollbarInfo, l: &taffy::Layout) -> Vec2 {
|
||||
Vec2::new(
|
||||
(info.content_size.x - l.content_box_width()) * self.data.scrolling.x,
|
||||
(info.content_size.y - l.content_box_height()) * self.data.scrolling.y,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn draw_all(&mut self, state: &mut DrawState, params: &DrawParams) {
|
||||
self.obj.draw(state, params);
|
||||
}
|
||||
|
||||
pub fn draw_scrollbars(
|
||||
&mut self,
|
||||
state: &mut DrawState,
|
||||
params: &DrawParams,
|
||||
info: &ScrollbarInfo,
|
||||
) {
|
||||
let (enabled_horiz, enabled_vert) = get_scroll_enabled(params.style);
|
||||
if !enabled_horiz && !enabled_vert {
|
||||
return;
|
||||
}
|
||||
|
||||
let transform = state.transform_stack.get();
|
||||
|
||||
let thickness = 6.0;
|
||||
let margin = 4.0;
|
||||
|
||||
let rect_params = drawing::Rectangle {
|
||||
color: drawing::Color::new(1.0, 1.0, 1.0, 0.0),
|
||||
border: 2.0,
|
||||
border_color: drawing::Color::new(1.0, 1.0, 1.0, 1.0),
|
||||
round_units: 2,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Horizontal handle
|
||||
if enabled_horiz && info.handle_size.x < 1.0 {
|
||||
state.primitives.push(drawing::RenderPrimitive {
|
||||
boundary: drawing::Boundary::from_pos_size(
|
||||
Vec2::new(
|
||||
transform.pos.x + transform.dim.x * (1.0 - info.handle_size.x) * self.data.scrolling.x,
|
||||
transform.pos.y + transform.dim.y - thickness - margin,
|
||||
),
|
||||
Vec2::new(transform.dim.x * info.handle_size.x, thickness),
|
||||
),
|
||||
depth: state.depth,
|
||||
transform: transform.transform,
|
||||
payload: drawing::PrimitivePayload::Rectangle(rect_params),
|
||||
});
|
||||
}
|
||||
|
||||
// Vertical handle
|
||||
if enabled_vert && info.handle_size.y < 1.0 {
|
||||
state.primitives.push(drawing::RenderPrimitive {
|
||||
boundary: drawing::Boundary::from_pos_size(
|
||||
Vec2::new(
|
||||
transform.pos.x + transform.dim.x - thickness - margin,
|
||||
transform.pos.y + transform.dim.y * (1.0 - info.handle_size.y) * self.data.scrolling.y,
|
||||
),
|
||||
Vec2::new(thickness, transform.dim.y * info.handle_size.y),
|
||||
),
|
||||
depth: state.depth,
|
||||
transform: transform.transform,
|
||||
payload: drawing::PrimitivePayload::Rectangle(rect_params),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn process_wheel(&mut self, params: &mut EventParams, wheel: &MouseWheelEvent) -> bool {
|
||||
let (enabled_horiz, enabled_vert) = get_scroll_enabled(params.style);
|
||||
if !enabled_horiz && !enabled_vert {
|
||||
return false;
|
||||
}
|
||||
|
||||
let l = params.taffy_layout;
|
||||
let overflow = Vec2::new(l.scroll_width(), l.scroll_height());
|
||||
if overflow.x == 0.0 && overflow.y == 0.0 {
|
||||
return false; // not overflowing
|
||||
}
|
||||
|
||||
let Some(info) = get_scrollbar_info(params.taffy_layout) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let step_pixels = 32.0;
|
||||
|
||||
if info.handle_size.x < 1.0 && wheel.pos.x != 0.0 {
|
||||
// Horizontal scrolling
|
||||
let mult = (1.0 / (l.content_box_width() - info.content_size.x)) * step_pixels;
|
||||
let new_scroll = (self.data.scrolling.x + wheel.shift.x * mult).clamp(0.0, 1.0);
|
||||
if self.data.scrolling.x != new_scroll {
|
||||
self.data.scrolling.x = new_scroll;
|
||||
*params.needs_redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
if info.handle_size.y < 1.0 && wheel.pos.y != 0.0 {
|
||||
// Vertical scrolling
|
||||
let mult = (1.0 / (l.content_box_height() - info.content_size.y)) * step_pixels;
|
||||
let new_scroll = (self.data.scrolling.y + wheel.shift.y * mult).clamp(0.0, 1.0);
|
||||
if self.data.scrolling.y != new_scroll {
|
||||
self.data.scrolling.y = new_scroll;
|
||||
*params.needs_redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn process_event(
|
||||
&mut self,
|
||||
widget_id: WidgetID,
|
||||
node_id: taffy::NodeId,
|
||||
event: &Event,
|
||||
params: &mut EventParams,
|
||||
) -> EventResult {
|
||||
let hovered = event.test_mouse_within_transform(params.transform_stack.get());
|
||||
|
||||
let mut just_clicked = false;
|
||||
match &event {
|
||||
Event::MouseDown(_) => {
|
||||
if self.data.hovered {
|
||||
self.data.pressed = true;
|
||||
}
|
||||
}
|
||||
Event::MouseUp(_) => {
|
||||
if self.data.pressed {
|
||||
self.data.pressed = false;
|
||||
just_clicked = self.data.hovered;
|
||||
}
|
||||
}
|
||||
Event::MouseWheel(e) => {
|
||||
if self.process_wheel(params, e) {
|
||||
return EventResult::Consumed;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// TODO: simplify this behemoth, I gave up arguing with the compiler
|
||||
for listener in &self.event_listeners {
|
||||
match listener {
|
||||
EventListener::MouseEnter(callback) => {
|
||||
if hovered && !self.data.hovered {
|
||||
let mut data = CallbackData {
|
||||
obj: self.obj.as_mut(),
|
||||
widget_data: &mut self.data,
|
||||
widgets: params.widgets,
|
||||
animations: params.animations,
|
||||
dirty_nodes: params.dirty_nodes,
|
||||
widget_id,
|
||||
node_id,
|
||||
needs_redraw: false,
|
||||
};
|
||||
callback(&mut data);
|
||||
if data.needs_redraw {
|
||||
*params.needs_redraw = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
EventListener::MouseLeave(callback) => {
|
||||
if !hovered && self.data.hovered {
|
||||
let mut data = CallbackData {
|
||||
obj: self.obj.as_mut(),
|
||||
widget_data: &mut self.data,
|
||||
widgets: params.widgets,
|
||||
animations: params.animations,
|
||||
dirty_nodes: params.dirty_nodes,
|
||||
widget_id,
|
||||
node_id,
|
||||
needs_redraw: false,
|
||||
};
|
||||
callback(&mut data);
|
||||
if data.needs_redraw {
|
||||
*params.needs_redraw = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
EventListener::MouseClick(callback) => {
|
||||
if just_clicked {
|
||||
let mut data = CallbackData {
|
||||
obj: self.obj.as_mut(),
|
||||
widget_data: &mut self.data,
|
||||
widgets: params.widgets,
|
||||
animations: params.animations,
|
||||
dirty_nodes: params.dirty_nodes,
|
||||
widget_id,
|
||||
node_id,
|
||||
needs_redraw: false,
|
||||
};
|
||||
callback(&mut data);
|
||||
if data.needs_redraw {
|
||||
*params.needs_redraw = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.data.hovered != hovered {
|
||||
self.data.hovered = hovered;
|
||||
}
|
||||
|
||||
EventResult::Pass
|
||||
}
|
||||
}
|
||||
55
wgui/src/widget/rectangle.rs
Normal file
55
wgui/src/widget/rectangle.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use crate::{
|
||||
drawing::{self, GradientMode},
|
||||
widget::util::WLength,
|
||||
};
|
||||
|
||||
use super::{WidgetObj, WidgetState};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RectangleParams {
|
||||
pub color: drawing::Color,
|
||||
pub color2: drawing::Color,
|
||||
pub gradient: GradientMode,
|
||||
|
||||
pub border: f32,
|
||||
pub border_color: drawing::Color,
|
||||
|
||||
pub round: WLength,
|
||||
}
|
||||
|
||||
pub struct Rectangle {
|
||||
pub params: RectangleParams,
|
||||
}
|
||||
|
||||
impl Rectangle {
|
||||
pub fn create(params: RectangleParams) -> anyhow::Result<WidgetState> {
|
||||
WidgetState::new(Box::new(Rectangle { params }))
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetObj for Rectangle {
|
||||
fn draw(&mut self, state: &mut super::DrawState, _params: &super::DrawParams) {
|
||||
let boundary = drawing::Boundary::construct(state.transform_stack);
|
||||
|
||||
let round_units = match self.params.round {
|
||||
WLength::Units(units) => units as u8,
|
||||
WLength::Percent(percent) => {
|
||||
(f32::min(boundary.size.x, boundary.size.y) * percent / 2.0) as u8
|
||||
}
|
||||
};
|
||||
|
||||
state.primitives.push(drawing::RenderPrimitive {
|
||||
boundary,
|
||||
depth: state.depth,
|
||||
transform: state.transform_stack.get().transform,
|
||||
payload: drawing::PrimitivePayload::Rectangle(drawing::Rectangle {
|
||||
color: self.params.color,
|
||||
color2: self.params.color2,
|
||||
gradient: self.params.gradient,
|
||||
border: self.params.border,
|
||||
border_color: self.params.border_color,
|
||||
round_units,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
82
wgui/src/widget/sprite.rs
Normal file
82
wgui/src/widget/sprite.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use cosmic_text::{Attrs, Buffer, Color, Shaping, Weight};
|
||||
|
||||
use crate::{
|
||||
drawing::{self},
|
||||
renderer_vk::text::{
|
||||
DEFAULT_METRICS, FONT_SYSTEM,
|
||||
custom_glyph::{CustomGlyph, CustomGlyphData},
|
||||
},
|
||||
};
|
||||
|
||||
use super::{WidgetObj, WidgetState};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SpriteBoxParams {
|
||||
pub glyph_data: Option<CustomGlyphData>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SpriteBox {
|
||||
params: SpriteBoxParams,
|
||||
}
|
||||
|
||||
impl SpriteBox {
|
||||
pub fn create(params: SpriteBoxParams) -> anyhow::Result<WidgetState> {
|
||||
WidgetState::new(Box::new(Self { params }))
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetObj for SpriteBox {
|
||||
fn draw(&mut self, state: &mut super::DrawState, _params: &super::DrawParams) {
|
||||
let boundary = drawing::Boundary::construct(state.transform_stack);
|
||||
|
||||
if let Some(glyph_data) = self.params.glyph_data.as_ref() {
|
||||
let glyph = CustomGlyph {
|
||||
data: glyph_data.clone(),
|
||||
left: 0.0,
|
||||
top: 0.0,
|
||||
width: boundary.size.x,
|
||||
height: boundary.size.y,
|
||||
color: Some(cosmic_text::Color::rgb(255, 255, 255)),
|
||||
snap_to_physical_pixel: true,
|
||||
};
|
||||
|
||||
state.primitives.push(drawing::RenderPrimitive {
|
||||
boundary,
|
||||
depth: state.depth,
|
||||
payload: drawing::PrimitivePayload::Sprite(Some(glyph)),
|
||||
transform: state.transform_stack.get().transform,
|
||||
});
|
||||
} else {
|
||||
// Source not set or not available, display error text
|
||||
let mut buffer = Buffer::new_empty(DEFAULT_METRICS);
|
||||
|
||||
{
|
||||
let mut font_system = FONT_SYSTEM.lock().unwrap(); // safe unwrap
|
||||
let mut buffer = buffer.borrow_with(&mut font_system);
|
||||
let attrs = Attrs::new()
|
||||
.color(Color::rgb(255, 0, 255))
|
||||
.weight(Weight::BOLD);
|
||||
|
||||
// set text last in order to avoid expensive re-shaping
|
||||
buffer.set_text("Error", &attrs, Shaping::Basic);
|
||||
}
|
||||
state.primitives.push(drawing::RenderPrimitive {
|
||||
boundary,
|
||||
depth: state.depth,
|
||||
payload: drawing::PrimitivePayload::Text(Rc::new(RefCell::new(buffer))),
|
||||
transform: state.transform_stack.get().transform,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&mut self,
|
||||
_known_dimensions: taffy::Size<Option<f32>>,
|
||||
_available_space: taffy::Size<taffy::AvailableSpace>,
|
||||
) -> taffy::Size<f32> {
|
||||
taffy::Size::ZERO
|
||||
}
|
||||
}
|
||||
117
wgui/src/widget/text.rs
Normal file
117
wgui/src/widget/text.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use cosmic_text::{Attrs, Buffer, Metrics, Shaping, Wrap};
|
||||
use taffy::AvailableSpace;
|
||||
|
||||
use crate::{
|
||||
drawing::{self, Boundary},
|
||||
renderer_vk::text::{FONT_SYSTEM, TextStyle},
|
||||
};
|
||||
|
||||
use super::{WidgetObj, WidgetState};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TextParams {
|
||||
pub content: String,
|
||||
pub style: TextStyle,
|
||||
}
|
||||
|
||||
pub struct TextLabel {
|
||||
params: TextParams,
|
||||
buffer: Rc<RefCell<Buffer>>,
|
||||
last_boundary: Boundary,
|
||||
}
|
||||
|
||||
impl TextLabel {
|
||||
pub fn create(params: TextParams) -> anyhow::Result<WidgetState> {
|
||||
let metrics = Metrics::from(¶ms.style);
|
||||
let attrs = Attrs::from(¶ms.style);
|
||||
let wrap = Wrap::from(¶ms.style);
|
||||
|
||||
let mut buffer = Buffer::new_empty(metrics);
|
||||
{
|
||||
let mut font_system = FONT_SYSTEM.lock().unwrap(); // safe unwrap
|
||||
let mut buffer = buffer.borrow_with(&mut font_system);
|
||||
buffer.set_wrap(wrap);
|
||||
|
||||
buffer.set_rich_text(
|
||||
[(params.content.as_str(), attrs)],
|
||||
&Attrs::new(),
|
||||
Shaping::Advanced,
|
||||
params.style.align.map(|a| a.into()),
|
||||
);
|
||||
}
|
||||
|
||||
WidgetState::new(Box::new(Self {
|
||||
params,
|
||||
buffer: Rc::new(RefCell::new(buffer)),
|
||||
last_boundary: Boundary::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: &str) {
|
||||
self.params.content = String::from(text);
|
||||
let attrs = Attrs::from(&self.params.style);
|
||||
let mut font_system = FONT_SYSTEM.lock().unwrap(); // safe unwrap
|
||||
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
buffer.set_rich_text(
|
||||
&mut font_system,
|
||||
[(self.params.content.as_str(), attrs)],
|
||||
&Attrs::new(),
|
||||
Shaping::Advanced,
|
||||
self.params.style.align.map(|a| a.into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetObj for TextLabel {
|
||||
fn draw(&mut self, state: &mut super::DrawState, _params: &super::DrawParams) {
|
||||
let boundary = drawing::Boundary::construct(state.transform_stack);
|
||||
|
||||
if self.last_boundary != boundary {
|
||||
self.last_boundary = boundary;
|
||||
let mut font_system = FONT_SYSTEM.lock().unwrap(); // safe unwrap
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
buffer.set_size(
|
||||
&mut font_system,
|
||||
Some(boundary.size.x),
|
||||
Some(boundary.size.y),
|
||||
);
|
||||
}
|
||||
|
||||
state.primitives.push(drawing::RenderPrimitive {
|
||||
boundary,
|
||||
depth: state.depth,
|
||||
payload: drawing::PrimitivePayload::Text(self.buffer.clone()),
|
||||
transform: state.transform_stack.get().transform,
|
||||
});
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&mut self,
|
||||
known_dimensions: taffy::Size<Option<f32>>,
|
||||
available_space: taffy::Size<taffy::AvailableSpace>,
|
||||
) -> taffy::Size<f32> {
|
||||
// Set width constraint
|
||||
let width_constraint = known_dimensions.width.or(match available_space.width {
|
||||
AvailableSpace::MinContent => Some(0.0),
|
||||
AvailableSpace::MaxContent => None,
|
||||
AvailableSpace::Definite(width) => Some(width),
|
||||
});
|
||||
|
||||
let mut font_system = FONT_SYSTEM.lock().unwrap(); // safe unwrap
|
||||
let mut buffer = self.buffer.borrow_mut();
|
||||
|
||||
buffer.set_size(&mut font_system, width_constraint, None);
|
||||
|
||||
// Determine measured size of text
|
||||
let (width, total_lines) = buffer
|
||||
.layout_runs()
|
||||
.fold((0.0, 0usize), |(width, total_lines), run| {
|
||||
(run.line_w.max(width), total_lines + 1)
|
||||
});
|
||||
let height = total_lines as f32 * buffer.metrics().line_height;
|
||||
taffy::Size { width, height }
|
||||
}
|
||||
}
|
||||
11
wgui/src/widget/util.rs
Normal file
11
wgui/src/widget/util.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum WLength {
|
||||
Units(f32),
|
||||
Percent(f32), // 0.0 - 1.0
|
||||
}
|
||||
|
||||
impl Default for WLength {
|
||||
fn default() -> Self {
|
||||
Self::Units(0.0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user