mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 21:30:03 -04:00
Add a fractal clock example to showcase painting performance
This commit is contained in:
@@ -37,6 +37,16 @@ impl Frame {
|
||||
outline: Some(Outline::new(1.0, color::white(128))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_color(mut self, fill_color: Option<Color>) -> Self {
|
||||
self.fill_color = fill_color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn outline(mut self, outline: Option<Outline>) -> Self {
|
||||
self.outline = outline;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
|
||||
@@ -48,20 +48,27 @@ impl<'open> Window<'open> {
|
||||
self
|
||||
}
|
||||
|
||||
/// This is quite a crap idea
|
||||
/// Usage: `Winmdow::new(...).mutate(|w| w.resize = w.resize.auto_expand_width(true))`
|
||||
/// Not sure this is a good interface for this.
|
||||
pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self {
|
||||
mutate(&mut self);
|
||||
self
|
||||
}
|
||||
|
||||
/// This is quite a crap idea
|
||||
/// Usage: `Winmdow::new(...).resize(|r| r.auto_expand_width(true))`
|
||||
/// Not sure this is a good interface for this.
|
||||
pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self {
|
||||
self.resize = mutate(self.resize);
|
||||
self
|
||||
}
|
||||
|
||||
/// Usage: `Winmdow::new(...).frame(|f| f.fill_color(Some(BLUE)))`
|
||||
/// Not sure this is a good interface for this.
|
||||
pub fn frame(mut self, frame: Frame) -> Self {
|
||||
self.frame = Some(frame);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn default_pos(mut self, default_pos: Pos2) -> Self {
|
||||
self.area = self.area.default_pos(default_pos);
|
||||
self
|
||||
@@ -72,6 +79,10 @@ impl<'open> Window<'open> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn default_rect(self, rect: Rect) -> Self {
|
||||
self.default_pos(rect.min).default_size(rect.size())
|
||||
}
|
||||
|
||||
pub fn min_size(mut self, min_size: Vec2) -> Self {
|
||||
self.resize = self.resize.min_size(min_size);
|
||||
self
|
||||
@@ -100,6 +111,13 @@ impl<'open> Window<'open> {
|
||||
self.scroll = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn scroll(mut self, scroll: bool) -> Self {
|
||||
if !scroll {
|
||||
self.scroll = None;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'open> Window<'open> {
|
||||
|
||||
@@ -80,6 +80,10 @@ impl Context {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn rect(&self) -> Rect {
|
||||
Rect::from_min_size(pos2(0.0, 0.0), self.input.screen_size)
|
||||
}
|
||||
|
||||
pub fn memory(&self) -> parking_lot::MutexGuard<'_, Memory> {
|
||||
self.memory.lock()
|
||||
}
|
||||
|
||||
7
emigui/src/examples.rs
Normal file
7
emigui/src/examples.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod app;
|
||||
mod fractal_clock;
|
||||
|
||||
pub use {
|
||||
app::{ExampleApp, ExampleWindow},
|
||||
fractal_clock::FractalClock,
|
||||
};
|
||||
@@ -3,37 +3,62 @@ use std::sync::Arc;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::{color::*, containers::*, widgets::*, *};
|
||||
use crate::{color::*, containers::*, examples::FractalClock, widgets::*, *};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct ExampleApp {
|
||||
has_initialized: bool,
|
||||
example_window: ExampleWindow,
|
||||
open_windows: OpenWindows,
|
||||
fractal_clock: FractalClock,
|
||||
}
|
||||
|
||||
impl ExampleApp {
|
||||
pub fn ui(&mut self, ctx: &Arc<Context>) {
|
||||
// TODO: Make it even simpler to show a window
|
||||
|
||||
// TODO: window manager for automatic positioning?
|
||||
|
||||
let ExampleApp {
|
||||
has_initialized,
|
||||
example_window,
|
||||
open_windows,
|
||||
fractal_clock,
|
||||
} = self;
|
||||
|
||||
if !*has_initialized {
|
||||
// #fragment end of URL:
|
||||
let location_hash = ctx
|
||||
.input()
|
||||
.web
|
||||
.as_ref()
|
||||
.map(|web| web.location_hash.as_str());
|
||||
if location_hash == Some("#clock") {
|
||||
open_windows.fractal_clock = true;
|
||||
}
|
||||
*has_initialized = true;
|
||||
}
|
||||
|
||||
Window::new("Examples")
|
||||
.default_pos(pos2(32.0, 100.0))
|
||||
.default_size(vec2(430.0, 600.0))
|
||||
.show(ctx, |ui| {
|
||||
show_menu_bar(ui, &mut self.open_windows);
|
||||
self.example_window.ui(ui);
|
||||
show_menu_bar(ui, open_windows);
|
||||
example_window.ui(ui);
|
||||
});
|
||||
|
||||
Window::new("Settings")
|
||||
.open(&mut self.open_windows.settings)
|
||||
.open(&mut open_windows.settings)
|
||||
.default_pos(pos2(500.0, 100.0))
|
||||
.default_size(vec2(350.0, 200.0))
|
||||
.default_size(vec2(350.0, 400.0))
|
||||
.show(ctx, |ui| {
|
||||
ctx.settings_ui(ui);
|
||||
});
|
||||
|
||||
Window::new("Inspection")
|
||||
.open(&mut self.open_windows.inspection)
|
||||
.open(&mut open_windows.inspection)
|
||||
.default_pos(pos2(500.0, 400.0))
|
||||
.default_size(vec2(400.0, 300.0))
|
||||
.show(ctx, |ui| {
|
||||
@@ -41,12 +66,14 @@ impl ExampleApp {
|
||||
});
|
||||
|
||||
Window::new("Memory")
|
||||
.open(&mut self.open_windows.memory)
|
||||
.open(&mut open_windows.memory)
|
||||
.default_pos(pos2(700.0, 350.0))
|
||||
.auto_sized()
|
||||
.show(ctx, |ui| {
|
||||
ctx.memory_ui(ui);
|
||||
});
|
||||
|
||||
fractal_clock.window(ctx, &mut open_windows.fractal_clock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +82,7 @@ struct OpenWindows {
|
||||
settings: bool,
|
||||
inspection: bool,
|
||||
memory: bool,
|
||||
fractal_clock: bool,
|
||||
}
|
||||
|
||||
impl Default for OpenWindows {
|
||||
@@ -63,6 +91,7 @@ impl Default for OpenWindows {
|
||||
settings: false,
|
||||
inspection: true,
|
||||
memory: false,
|
||||
fractal_clock: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,9 +104,13 @@ fn show_menu_bar(ui: &mut Ui, windows: &mut OpenWindows) {
|
||||
ui.add(Button::new("Don't Quit"));
|
||||
});
|
||||
menu::menu(ui, "Windows", |ui| {
|
||||
// TODO: open on top when clicking a new.
|
||||
// Maybe an Window or Area can detect that: if wasn't open last frame, but is now,
|
||||
// then automatically go to front?
|
||||
ui.add(Checkbox::new(&mut windows.settings, "Settings"));
|
||||
ui.add(Checkbox::new(&mut windows.inspection, "Inspection"));
|
||||
ui.add(Checkbox::new(&mut windows.memory, "Memory"));
|
||||
ui.add(Checkbox::new(&mut windows.fractal_clock, "Fractal Clock"));
|
||||
});
|
||||
menu::menu(ui, "About", |ui| {
|
||||
ui.add(label!("This is Emigui, but you already knew that!"));
|
||||
@@ -325,7 +358,7 @@ impl Painting {
|
||||
|
||||
for line in &self.lines {
|
||||
if line.len() >= 2 {
|
||||
ui.add_paint_cmd(PaintCmd::Line {
|
||||
ui.add_paint_cmd(PaintCmd::LinePath {
|
||||
points: line.iter().map(|p| canvas_corner + *p).collect(),
|
||||
color: LIGHT_GRAY,
|
||||
width: 2.0,
|
||||
194
emigui/src/examples/fractal_clock.rs
Normal file
194
emigui/src/examples/fractal_clock.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::{containers::*, widgets::*, *};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct FractalClock {
|
||||
paused: bool,
|
||||
time: f64,
|
||||
zoom: f32,
|
||||
start_line_width: f32,
|
||||
depth: usize,
|
||||
length_factor: f32,
|
||||
luminance_factor: f32,
|
||||
width_factor: f32,
|
||||
}
|
||||
|
||||
impl Default for FractalClock {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
paused: false,
|
||||
time: 0.0,
|
||||
zoom: 0.25,
|
||||
start_line_width: 2.5,
|
||||
depth: 9,
|
||||
length_factor: 0.8,
|
||||
luminance_factor: 0.8,
|
||||
width_factor: 0.9,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FractalClock {
|
||||
pub fn window(&mut self, ctx: &Arc<Context>, open: &mut bool) {
|
||||
Window::new("FractalClock")
|
||||
.open(open)
|
||||
.default_rect(ctx.rect().expand(-40.0))
|
||||
.scroll(false)
|
||||
// Dark background frame to make it pop:
|
||||
.frame(Frame::window(&ctx.style()).fill_color(Some(color::black(250))))
|
||||
.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
self.fractal_ui(ui);
|
||||
|
||||
// TODO: background frame etc
|
||||
Frame::popup(ui.style())
|
||||
.fill_color(Some(color::gray(34, 160)))
|
||||
.outline(None)
|
||||
.show(&mut ui.left_column(320.0), |ui| self.options_ui(ui));
|
||||
}
|
||||
|
||||
fn options_ui(&mut self, ui: &mut Ui) {
|
||||
let time = if let Some(seconds_since_midnight) = ui.input().seconds_since_midnight {
|
||||
ui.add(label!(
|
||||
"Local time: {:02}:{:02}:{:02}.{:03}",
|
||||
(seconds_since_midnight.rem_euclid(24.0 * 60.0 * 60.0) / 3600.0).floor(),
|
||||
(seconds_since_midnight.rem_euclid(60.0 * 60.0) / 60.0).floor(),
|
||||
(seconds_since_midnight.rem_euclid(60.0)).floor(),
|
||||
(seconds_since_midnight.rem_euclid(1.0) * 1000.0).floor()
|
||||
));
|
||||
seconds_since_midnight
|
||||
} else {
|
||||
ui.add(label!(
|
||||
"The fractal_clock clock is not showing the correct time"
|
||||
));
|
||||
ui.input().time
|
||||
};
|
||||
|
||||
ui.add(Checkbox::new(&mut self.paused, "Paused"));
|
||||
ui.add(Slider::f32(&mut self.zoom, 0.0..=1.0).text("zoom"));
|
||||
ui.add(Slider::f32(&mut self.start_line_width, 0.0..=5.0).text("Start line width"));
|
||||
ui.add(Slider::usize(&mut self.depth, 0..=14).text("depth"));
|
||||
ui.add(Slider::f32(&mut self.length_factor, 0.0..=1.0).text("length factor"));
|
||||
ui.add(Slider::f32(&mut self.luminance_factor, 0.0..=1.0).text("luminance factor"));
|
||||
ui.add(Slider::f32(&mut self.width_factor, 0.0..=1.0).text("width factor"));
|
||||
if ui.add(Button::new("Reset")).clicked {
|
||||
*self = Default::default();
|
||||
}
|
||||
|
||||
if !self.paused {
|
||||
self.time = time;
|
||||
}
|
||||
|
||||
ui.add(
|
||||
Hyperlink::new("http://www.dqd.com/~mayoff/programs/FractalClock/")
|
||||
.text("Inspired by a screensaver by Rob Mayoff"),
|
||||
);
|
||||
}
|
||||
|
||||
fn fractal_ui(&mut self, ui: &mut Ui) {
|
||||
struct Hand {
|
||||
length: f32,
|
||||
angle: f32,
|
||||
vec: Vec2,
|
||||
}
|
||||
|
||||
impl Hand {
|
||||
fn from_length_angle(length: f32, angle: f32) -> Self {
|
||||
Self {
|
||||
length,
|
||||
angle,
|
||||
vec: length * Vec2::angled(angle),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let angle_from_period =
|
||||
|period| TAU * (self.time.rem_euclid(period) / period) as f32 - TAU / 4.0;
|
||||
|
||||
let hands = [
|
||||
// Second hand:
|
||||
Hand::from_length_angle(self.length_factor, angle_from_period(60.0)),
|
||||
// Minute hand:
|
||||
Hand::from_length_angle(self.length_factor, angle_from_period(60.0 * 60.0)),
|
||||
// Hour hand:
|
||||
Hand::from_length_angle(0.5, angle_from_period(12.0 * 60.0 * 60.0)),
|
||||
];
|
||||
|
||||
let rect = ui.available_rect_min();
|
||||
|
||||
let scale = self.zoom * rect.width().min(rect.height());
|
||||
let mut paint_line = |points: [Pos2; 2], color: Color, width: f32| {
|
||||
let line = [
|
||||
rect.center() + scale * points[0].to_vec2(),
|
||||
rect.center() + scale * points[1].to_vec2(),
|
||||
];
|
||||
|
||||
ui.add_paint_cmd(PaintCmd::line_segment([line[0], line[1]], color, width));
|
||||
};
|
||||
|
||||
let hand_rotations = [
|
||||
hands[0].angle - hands[2].angle,
|
||||
hands[1].angle - hands[2].angle,
|
||||
];
|
||||
|
||||
let hand_rotors = [
|
||||
hands[0].length * Vec2::angled(hand_rotations[0]),
|
||||
hands[1].length * Vec2::angled(hand_rotations[1]),
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Node {
|
||||
pos: Pos2,
|
||||
dir: Vec2,
|
||||
}
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
|
||||
let mut width = self.start_line_width;
|
||||
|
||||
for (i, hand) in hands.iter().enumerate() {
|
||||
let center = pos2(0.0, 0.0);
|
||||
let end = center + hand.vec;
|
||||
paint_line([center, end], color::additive_gray(255), width);
|
||||
if i < 2 {
|
||||
nodes.push(Node {
|
||||
pos: end,
|
||||
dir: hand.vec,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mut luminance = 0.7; // Start dimmer than main hands
|
||||
|
||||
let mut new_nodes = Vec::new();
|
||||
for _ in 0..self.depth {
|
||||
new_nodes.clear();
|
||||
new_nodes.reserve(nodes.len() * 2);
|
||||
|
||||
luminance *= self.luminance_factor;
|
||||
width *= self.width_factor;
|
||||
|
||||
let luminance_u8 = (255.0 * luminance).round() as u8;
|
||||
|
||||
for rotor in &hand_rotors {
|
||||
for a in &nodes {
|
||||
let new_dir = rotor.rotate_other(a.dir);
|
||||
let b = Node {
|
||||
pos: a.pos + new_dir,
|
||||
dir: new_dir,
|
||||
};
|
||||
paint_line([a.pos, b.pos], color::additive_gray(luminance_u8), width);
|
||||
new_nodes.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
std::mem::swap(&mut nodes, &mut new_nodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
use crate::math::*;
|
||||
|
||||
/// What the integration gives to the gui.
|
||||
/// All coordinates in emigui is in point/logical coordinates.
|
||||
#[derive(Clone, Debug, Default, serde_derive::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct RawInput {
|
||||
/// Is the button currently down?
|
||||
@@ -15,6 +17,7 @@ pub struct RawInput {
|
||||
pub scroll_delta: Vec2,
|
||||
|
||||
/// Size of the screen in points.
|
||||
/// TODO: this should be screen_rect for easy sandboxing.
|
||||
pub screen_size: Vec2,
|
||||
|
||||
/// Also known as device pixel ratio, > 1 for HDPI screens.
|
||||
@@ -23,6 +26,9 @@ pub struct RawInput {
|
||||
/// Time in seconds. Relative to whatever. Used for animation.
|
||||
pub time: f64,
|
||||
|
||||
/// Local time. Only used for the clock in the example app.
|
||||
pub seconds_since_midnight: Option<f64>,
|
||||
|
||||
/// Files has been dropped into the window.
|
||||
pub dropped_files: Vec<std::path::PathBuf>,
|
||||
|
||||
@@ -31,6 +37,9 @@ pub struct RawInput {
|
||||
|
||||
/// In-order events received this frame
|
||||
pub events: Vec<Event>,
|
||||
|
||||
/// Web-only input
|
||||
pub web: Option<Web>,
|
||||
}
|
||||
|
||||
/// What emigui maintains
|
||||
@@ -74,6 +83,9 @@ pub struct GuiInput {
|
||||
/// Time since last frame, in seconds.
|
||||
pub dt: f32,
|
||||
|
||||
/// Local time. Only used for the clock in the example app.
|
||||
pub seconds_since_midnight: Option<f64>,
|
||||
|
||||
/// Files has been dropped into the window.
|
||||
pub dropped_files: Vec<std::path::PathBuf>,
|
||||
|
||||
@@ -82,9 +94,20 @@ pub struct GuiInput {
|
||||
|
||||
/// In-order events received this frame
|
||||
pub events: Vec<Event>,
|
||||
|
||||
/// Web-only input
|
||||
pub web: Option<Web>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde_derive::Deserialize)]
|
||||
#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Web {
|
||||
pub location: String,
|
||||
/// i.e. "#fragment"
|
||||
pub location_hash: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Event {
|
||||
Copy,
|
||||
@@ -97,7 +120,7 @@ pub enum Event {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde_derive::Deserialize)]
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Key {
|
||||
Alt,
|
||||
@@ -145,9 +168,11 @@ impl GuiInput {
|
||||
pixels_per_point: new.pixels_per_point,
|
||||
time: new.time,
|
||||
dt,
|
||||
seconds_since_midnight: new.seconds_since_midnight,
|
||||
dropped_files: new.dropped_files.clone(),
|
||||
hovered_files: new.hovered_files.clone(),
|
||||
events: new.events.clone(),
|
||||
web: new.web.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,6 +191,9 @@ impl RawInput {
|
||||
ui.add(label!("events: {:?}", self.events));
|
||||
ui.add(label!("dropped_files: {:?}", self.dropped_files));
|
||||
ui.add(label!("hovered_files: {:?}", self.hovered_files));
|
||||
if let Some(web) = &self.web {
|
||||
web.ui(ui);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,5 +217,16 @@ impl GuiInput {
|
||||
ui.add(label!("events: {:?}", self.events));
|
||||
ui.add(label!("dropped_files: {:?}", self.dropped_files));
|
||||
ui.add(label!("hovered_files: {:?}", self.hovered_files));
|
||||
if let Some(web) = &self.web {
|
||||
web.ui(ui);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Web {
|
||||
pub fn ui(&self, ui: &mut crate::Ui) {
|
||||
use crate::label;
|
||||
ui.add(label!("location: '{}'", self.location));
|
||||
ui.add(label!("location_hash: '{}'", self.location_hash));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
pub mod color;
|
||||
pub mod containers;
|
||||
mod context;
|
||||
pub mod example_app;
|
||||
pub mod examples;
|
||||
mod font;
|
||||
mod fonts;
|
||||
mod id;
|
||||
|
||||
@@ -65,6 +65,16 @@ impl Vec2 {
|
||||
vec2(angle.cos(), angle.sin())
|
||||
}
|
||||
|
||||
/// Use this vector as a rotor, rotating something else.
|
||||
/// Example: Vec2::angled(angle).rotate_other(some_vec)
|
||||
#[must_use]
|
||||
pub fn rotate_other(self, v: Vec2) -> Self {
|
||||
Self {
|
||||
x: v.x * self.x + v.y * -self.y,
|
||||
y: v.x * self.y + v.y * self.x,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn floor(self) -> Self {
|
||||
vec2(self.x.floor(), self.y.floor())
|
||||
|
||||
@@ -256,6 +256,10 @@ impl Ui {
|
||||
self.finite_bottom_right() - self.cursor
|
||||
}
|
||||
|
||||
pub fn available_rect_min(&self) -> Rect {
|
||||
Rect::from_min_size(self.cursor, self.available_space_min())
|
||||
}
|
||||
|
||||
pub fn direction(&self) -> Direction {
|
||||
self.dir
|
||||
}
|
||||
|
||||
@@ -133,6 +133,12 @@ impl Hyperlink {
|
||||
url,
|
||||
}
|
||||
}
|
||||
|
||||
/// Show some other text than the url
|
||||
pub fn text(mut self, text: impl Into<String>) -> Self {
|
||||
self.text = text.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Hyperlink {
|
||||
@@ -154,6 +160,7 @@ impl Widget for Hyperlink {
|
||||
|
||||
if interact.hovered {
|
||||
// Underline:
|
||||
// TODO: underline spaces between words too.
|
||||
for fragment in &text {
|
||||
let pos = interact.rect.min;
|
||||
let y = pos.y + fragment.y_offset + line_spacing;
|
||||
@@ -260,7 +267,7 @@ impl<'a> Widget for Checkbox<'a> {
|
||||
let id = ui.make_position_id();
|
||||
let text_style = TextStyle::Button;
|
||||
let font = &ui.fonts()[text_style];
|
||||
let (text, text_size) = font.layout_multiline(&self.text, ui.available_width());
|
||||
let (text, text_size) = font.layout_single_line(&self.text);
|
||||
let interact = ui.reserve_space(
|
||||
ui.style().button_padding
|
||||
+ vec2(ui.style().start_icon_width, 0.0)
|
||||
|
||||
Reference in New Issue
Block a user