summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/keyboard/event.rs3
-rw-r--r--examples/color_palette/src/main.rs32
-rw-r--r--examples/integration/src/controls.rs39
-rw-r--r--examples/integration/src/main.rs34
-rw-r--r--examples/progress_bar/src/main.rs15
-rw-r--r--examples/tour/src/main.rs68
-rw-r--r--glow/src/backend.rs6
-rw-r--r--glow/src/settings.rs6
-rw-r--r--glow/src/widget/slider.rs2
-rw-r--r--glow/src/window/compositor.rs8
-rw-r--r--glutin/src/application.rs52
-rw-r--r--graphics/src/backend.rs3
-rw-r--r--graphics/src/lib.rs6
-rw-r--r--graphics/src/widget/button.rs2
-rw-r--r--graphics/src/widget/canvas/path/builder.rs2
-rw-r--r--graphics/src/widget/slider.rs4
-rw-r--r--graphics/src/widget/text.rs4
-rw-r--r--graphics/src/widget/text_input.rs6
-rw-r--r--graphics/src/window/compositor.rs3
-rw-r--r--graphics/src/window/gl_compositor.rs3
-rw-r--r--native/Cargo.toml1
-rw-r--r--native/src/debug/basic.rs10
-rw-r--r--native/src/program/state.rs24
-rw-r--r--native/src/renderer/null.rs9
-rw-r--r--native/src/user_interface.rs46
-rw-r--r--native/src/widget/checkbox.rs10
-rw-r--r--native/src/widget/pane_grid.rs20
-rw-r--r--native/src/widget/pane_grid/node.rs28
-rw-r--r--native/src/widget/pane_grid/state.rs21
-rw-r--r--native/src/widget/radio.rs10
-rw-r--r--native/src/widget/slider.rs83
-rw-r--r--native/src/widget/text.rs8
-rw-r--r--native/src/widget/text_input.rs46
-rw-r--r--src/application.rs38
-rw-r--r--src/sandbox.rs37
-rw-r--r--src/settings.rs34
-rw-r--r--src/widget.rs2
-rw-r--r--src/window.rs3
-rw-r--r--src/window/icon.rs132
-rw-r--r--src/window/settings.rs33
-rw-r--r--style/src/lib.rs2
-rw-r--r--web/Cargo.toml5
-rw-r--r--web/src/lib.rs19
-rw-r--r--web/src/widget/button.rs14
-rw-r--r--web/src/widget/checkbox.rs29
-rw-r--r--web/src/widget/image.rs27
-rw-r--r--web/src/widget/radio.rs46
-rw-r--r--web/src/widget/slider.rs70
-rw-r--r--web/src/widget/text.rs10
-rw-r--r--web/src/widget/text_input.rs54
-rw-r--r--wgpu/src/backend.rs8
-rw-r--r--wgpu/src/lib.rs2
-rw-r--r--wgpu/src/settings.rs6
-rw-r--r--wgpu/src/triangle.rs38
-rw-r--r--wgpu/src/widget/slider.rs2
-rw-r--r--wgpu/src/window/compositor.rs17
-rw-r--r--winit/src/application.rs91
-rw-r--r--winit/src/conversion.rs15
-rw-r--r--winit/src/settings.rs27
59 files changed, 1064 insertions, 311 deletions
diff --git a/core/src/keyboard/event.rs b/core/src/keyboard/event.rs
index bc8437a8..d142c3bc 100644
--- a/core/src/keyboard/event.rs
+++ b/core/src/keyboard/event.rs
@@ -28,4 +28,7 @@ pub enum Event {
/// A unicode character was received.
CharacterReceived(char),
+
+ /// The keyboard modifiers have changed.
+ ModifiersChanged(ModifiersState),
}
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
index cec6ac79..3186deff 100644
--- a/examples/color_palette/src/main.rs
+++ b/examples/color_palette/src/main.rs
@@ -269,7 +269,7 @@ struct ColorPicker<C: ColorSpace> {
trait ColorSpace: Sized {
const LABEL: &'static str;
- const COMPONENT_RANGES: [RangeInclusive<f32>; 3];
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3];
fn new(a: f32, b: f32, c: f32) -> Self;
@@ -284,13 +284,25 @@ impl<C: 'static + ColorSpace + Copy> ColorPicker<C> {
let [s1, s2, s3] = &mut self.sliders;
let [cr1, cr2, cr3] = C::COMPONENT_RANGES;
+ fn slider<C>(
+ state: &mut slider::State,
+ range: RangeInclusive<f64>,
+ component: f32,
+ update: impl Fn(f32) -> C + 'static,
+ ) -> Slider<f64, C> {
+ Slider::new(state, range, f64::from(component), move |v| {
+ update(v as f32)
+ })
+ .step(0.01)
+ }
+
Row::new()
.spacing(10)
.align_items(Align::Center)
.push(Text::new(C::LABEL).width(Length::Units(50)))
- .push(Slider::new(s1, cr1, c1, move |v| C::new(v, c2, c3)))
- .push(Slider::new(s2, cr2, c2, move |v| C::new(c1, v, c3)))
- .push(Slider::new(s3, cr3, c3, move |v| C::new(c1, c2, v)))
+ .push(slider(s1, cr1, c1, move |v| C::new(v, c2, c3)))
+ .push(slider(s2, cr2, c2, move |v| C::new(c1, v, c3)))
+ .push(slider(s3, cr3, c3, move |v| C::new(c1, c2, v)))
.push(
Text::new(color.to_string())
.width(Length::Units(185))
@@ -302,7 +314,7 @@ impl<C: 'static + ColorSpace + Copy> ColorPicker<C> {
impl ColorSpace for Color {
const LABEL: &'static str = "RGB";
- const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
[0.0..=1.0, 0.0..=1.0, 0.0..=1.0];
fn new(r: f32, g: f32, b: f32) -> Self {
@@ -325,7 +337,7 @@ impl ColorSpace for Color {
impl ColorSpace for palette::Hsl {
const LABEL: &'static str = "HSL";
- const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
[0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
fn new(hue: f32, saturation: f32, lightness: f32) -> Self {
@@ -356,7 +368,7 @@ impl ColorSpace for palette::Hsl {
impl ColorSpace for palette::Hsv {
const LABEL: &'static str = "HSV";
- const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
[0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
fn new(hue: f32, saturation: f32, value: f32) -> Self {
@@ -379,7 +391,7 @@ impl ColorSpace for palette::Hsv {
impl ColorSpace for palette::Hwb {
const LABEL: &'static str = "HWB";
- const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
[0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
fn new(hue: f32, whiteness: f32, blackness: f32) -> Self {
@@ -410,7 +422,7 @@ impl ColorSpace for palette::Hwb {
impl ColorSpace for palette::Lab {
const LABEL: &'static str = "Lab";
- const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
[0.0..=100.0, -128.0..=127.0, -128.0..=127.0];
fn new(l: f32, a: f32, b: f32) -> Self {
@@ -428,7 +440,7 @@ impl ColorSpace for palette::Lab {
impl ColorSpace for palette::Lch {
const LABEL: &'static str = "Lch";
- const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
[0.0..=100.0, 0.0..=128.0, 0.0..=360.0];
fn new(l: f32, chroma: f32, hue: f32) -> Self {
diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs
index e6e74995..824f9f53 100644
--- a/examples/integration/src/controls.rs
+++ b/examples/integration/src/controls.rs
@@ -48,24 +48,33 @@ impl Program for Controls {
let sliders = Row::new()
.width(Length::Units(500))
.spacing(20)
- .push(Slider::new(r, 0.0..=1.0, background_color.r, move |r| {
- Message::BackgroundColorChanged(Color {
- r,
- ..background_color
+ .push(
+ Slider::new(r, 0.0..=1.0, background_color.r, move |r| {
+ Message::BackgroundColorChanged(Color {
+ r,
+ ..background_color
+ })
})
- }))
- .push(Slider::new(g, 0.0..=1.0, background_color.g, move |g| {
- Message::BackgroundColorChanged(Color {
- g,
- ..background_color
+ .step(0.01),
+ )
+ .push(
+ Slider::new(g, 0.0..=1.0, background_color.g, move |g| {
+ Message::BackgroundColorChanged(Color {
+ g,
+ ..background_color
+ })
})
- }))
- .push(Slider::new(b, 0.0..=1.0, background_color.b, move |b| {
- Message::BackgroundColorChanged(Color {
- b,
- ..background_color
+ .step(0.01),
+ )
+ .push(
+ Slider::new(b, 0.0..=1.0, background_color.b, move |b| {
+ Message::BackgroundColorChanged(Color {
+ b,
+ ..background_color
+ })
})
- }));
+ .step(0.01),
+ );
Row::new()
.width(Length::Fill)
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
index db8b4366..33d4c361 100644
--- a/examples/integration/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -5,9 +5,10 @@ use controls::Controls;
use scene::Scene;
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
-use iced_winit::{futures, program, winit, Debug, Size};
+use iced_winit::{conversion, futures, program, winit, Debug, Size};
use winit::{
+ dpi::PhysicalPosition,
event::{Event, ModifiersState, WindowEvent},
event_loop::{ControlFlow, EventLoop},
};
@@ -24,6 +25,7 @@ pub fn main() {
Size::new(physical_size.width, physical_size.height),
window.scale_factor(),
);
+ let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
let mut modifiers = ModifiersState::default();
// Initialize wgpu
@@ -79,6 +81,7 @@ pub fn main() {
let mut state = program::State::new(
controls,
viewport.logical_size(),
+ conversion::cursor_position(cursor_position, viewport.scale_factor()),
&mut renderer,
&mut debug,
);
@@ -91,6 +94,9 @@ pub fn main() {
match event {
Event::WindowEvent { event, .. } => {
match event {
+ WindowEvent::CursorMoved { position, .. } => {
+ cursor_position = position;
+ }
WindowEvent::ModifiersChanged(new_modifiers) => {
modifiers = new_modifiers;
}
@@ -105,7 +111,6 @@ pub fn main() {
WindowEvent::CloseRequested => {
*control_flow = ControlFlow::Exit;
}
-
_ => {}
}
@@ -119,16 +124,23 @@ pub fn main() {
}
}
Event::MainEventsCleared => {
- // We update iced
- let _ = state.update(
- None,
- viewport.logical_size(),
- &mut renderer,
- &mut debug,
- );
+ // If there are events pending
+ if !state.is_queue_empty() {
+ // We update iced
+ let _ = state.update(
+ viewport.logical_size(),
+ conversion::cursor_position(
+ cursor_position,
+ viewport.scale_factor(),
+ ),
+ None,
+ &mut renderer,
+ &mut debug,
+ );
- // and request a redraw
- window.request_redraw();
+ // and request a redraw
+ window.request_redraw();
+ }
}
Event::RedrawRequested(_) => {
if resized {
diff --git a/examples/progress_bar/src/main.rs b/examples/progress_bar/src/main.rs
index 43b09928..51b56eda 100644
--- a/examples/progress_bar/src/main.rs
+++ b/examples/progress_bar/src/main.rs
@@ -36,12 +36,15 @@ impl Sandbox for Progress {
Column::new()
.padding(20)
.push(ProgressBar::new(0.0..=100.0, self.value))
- .push(Slider::new(
- &mut self.progress_bar_slider,
- 0.0..=100.0,
- self.value,
- Message::SliderChanged,
- ))
+ .push(
+ Slider::new(
+ &mut self.progress_bar_slider,
+ 0.0..=100.0,
+ self.value,
+ Message::SliderChanged,
+ )
+ .step(0.01),
+ )
.into()
}
}
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index c9678b9d..4f8a4b32 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -190,7 +190,7 @@ enum Step {
Welcome,
Slider {
state: slider::State,
- value: u16,
+ value: u8,
},
RowsAndColumns {
layout: Layout,
@@ -222,13 +222,13 @@ enum Step {
#[derive(Debug, Clone)]
pub enum StepMessage {
- SliderChanged(f32),
+ SliderChanged(u8),
LayoutChanged(Layout),
- SpacingChanged(f32),
- TextSizeChanged(f32),
+ SpacingChanged(u16),
+ TextSizeChanged(u16),
TextColorChanged(Color),
LanguageSelected(Language),
- ImageWidthChanged(f32),
+ ImageWidthChanged(u16),
InputChanged(String),
ToggleSecureInput(bool),
DebugToggled(bool),
@@ -249,12 +249,12 @@ impl<'a> Step {
}
StepMessage::SliderChanged(new_value) => {
if let Step::Slider { value, .. } = self {
- *value = new_value.round() as u16;
+ *value = new_value;
}
}
StepMessage::TextSizeChanged(new_size) => {
if let Step::Text { size, .. } = self {
- *size = new_size.round() as u16;
+ *size = new_size;
}
}
StepMessage::TextColorChanged(new_color) => {
@@ -269,12 +269,12 @@ impl<'a> Step {
}
StepMessage::SpacingChanged(new_spacing) => {
if let Step::RowsAndColumns { spacing, .. } = self {
- *spacing = new_spacing.round() as u16;
+ *spacing = new_spacing;
}
}
StepMessage::ImageWidthChanged(new_width) => {
if let Step::Image { width, .. } = self {
- *width = new_width.round() as u16;
+ *width = new_width;
}
}
StepMessage::InputChanged(new_value) => {
@@ -384,7 +384,7 @@ impl<'a> Step {
fn slider(
state: &'a mut slider::State,
- value: u16,
+ value: u8,
) -> Column<'a, StepMessage> {
Self::container("Slider")
.push(Text::new(
@@ -397,8 +397,8 @@ impl<'a> Step {
))
.push(Slider::new(
state,
- 0.0..=100.0,
- value as f32,
+ 0..=100,
+ value,
StepMessage::SliderChanged,
))
.push(
@@ -444,8 +444,8 @@ impl<'a> Step {
.spacing(10)
.push(Slider::new(
spacing_slider,
- 0.0..=80.0,
- spacing as f32,
+ 0..=80,
+ spacing,
StepMessage::SpacingChanged,
))
.push(
@@ -486,30 +486,25 @@ impl<'a> Step {
)
.push(Slider::new(
size_slider,
- 10.0..=70.0,
- size as f32,
+ 10..=70,
+ size,
StepMessage::TextSizeChanged,
));
let [red, green, blue] = color_sliders;
+
+ let color_sliders = Row::new()
+ .spacing(10)
+ .push(color_slider(red, color.r, move |r| Color { r, ..color }))
+ .push(color_slider(green, color.g, move |g| Color { g, ..color }))
+ .push(color_slider(blue, color.b, move |b| Color { b, ..color }));
+
let color_section = Column::new()
.padding(20)
.spacing(20)
.push(Text::new("And its color:"))
.push(Text::new(&format!("{:?}", color)).color(color))
- .push(
- Row::new()
- .spacing(10)
- .push(Slider::new(red, 0.0..=1.0, color.r, move |r| {
- StepMessage::TextColorChanged(Color { r, ..color })
- }))
- .push(Slider::new(green, 0.0..=1.0, color.g, move |g| {
- StepMessage::TextColorChanged(Color { g, ..color })
- }))
- .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| {
- StepMessage::TextColorChanged(Color { b, ..color })
- })),
- );
+ .push(color_sliders);
Self::container("Text")
.push(Text::new(
@@ -559,8 +554,8 @@ impl<'a> Step {
.push(ferris(width))
.push(Slider::new(
slider,
- 100.0..=500.0,
- width as f32,
+ 100..=500,
+ width,
StepMessage::ImageWidthChanged,
))
.push(
@@ -706,6 +701,17 @@ fn button<'a, Message>(
.min_width(100)
}
+fn color_slider(
+ state: &mut slider::State,
+ component: f32,
+ update: impl Fn(f32) -> Color + 'static,
+) -> Slider<f64, StepMessage> {
+ Slider::new(state, 0.0..=1.0, f64::from(component), move |c| {
+ StepMessage::TextColorChanged(update(c as f32))
+ })
+ .step(0.01)
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
Rust,
diff --git a/glow/src/backend.rs b/glow/src/backend.rs
index 882dd605..8b5b4f9c 100644
--- a/glow/src/backend.rs
+++ b/glow/src/backend.rs
@@ -18,6 +18,7 @@ pub struct Backend {
quad_pipeline: quad::Pipeline,
text_pipeline: text::Pipeline,
triangle_pipeline: triangle::Pipeline,
+ default_text_size: u16,
}
impl Backend {
@@ -33,6 +34,7 @@ impl Backend {
quad_pipeline,
text_pipeline,
triangle_pipeline,
+ default_text_size: settings.default_text_size,
}
}
@@ -192,6 +194,10 @@ impl backend::Text for Backend {
const ICON_FONT: Font = font::ICONS;
const CHECKMARK_ICON: char = font::CHECKMARK_ICON;
+ fn default_size(&self) -> u16 {
+ self.default_text_size
+ }
+
fn measure(
&self,
contents: &str,
diff --git a/glow/src/settings.rs b/glow/src/settings.rs
index dce30029..c2c605ef 100644
--- a/glow/src/settings.rs
+++ b/glow/src/settings.rs
@@ -11,6 +11,11 @@ pub struct Settings {
/// If `None` is provided, a default system font will be chosen.
pub default_font: Option<&'static [u8]>,
+ /// The default size of text.
+ ///
+ /// By default, it will be set to 20.
+ pub default_text_size: u16,
+
/// The antialiasing strategy that will be used for triangle primitives.
pub antialiasing: Option<Antialiasing>,
}
@@ -19,6 +24,7 @@ impl Default for Settings {
fn default() -> Settings {
Settings {
default_font: None,
+ default_text_size: 20,
antialiasing: None,
}
}
diff --git a/glow/src/widget/slider.rs b/glow/src/widget/slider.rs
index cf036829..3a8c2595 100644
--- a/glow/src/widget/slider.rs
+++ b/glow/src/widget/slider.rs
@@ -13,4 +13,4 @@ pub use iced_native::slider::State;
/// values.
///
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
-pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Renderer>;
+pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Renderer>;
diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs
index 2f504ff7..3ad10b59 100644
--- a/glow/src/window/compositor.rs
+++ b/glow/src/window/compositor.rs
@@ -1,4 +1,4 @@
-use crate::{Backend, Renderer, Settings, Viewport};
+use crate::{Backend, Color, Renderer, Settings, Viewport};
use core::ffi::c_void;
use glow::HasContext;
@@ -21,8 +21,6 @@ impl iced_graphics::window::GLCompositor for Compositor {
) -> (Self, Self::Renderer) {
let gl = glow::Context::from_loader_function(loader_function);
- gl.clear_color(1.0, 1.0, 1.0, 1.0);
-
// Enable auto-conversion from/to sRGB
gl.enable(glow::FRAMEBUFFER_SRGB);
@@ -60,12 +58,16 @@ impl iced_graphics::window::GLCompositor for Compositor {
&mut self,
renderer: &mut Self::Renderer,
viewport: &Viewport,
+ color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction {
let gl = &self.gl;
+ let [r, g, b, a] = color.into_linear();
+
unsafe {
+ gl.clear_color(r, g, b, a);
gl.clear(glow::COLOR_BUFFER_BIT);
}
diff --git a/glutin/src/application.rs b/glutin/src/application.rs
index c777a13b..3be9b65f 100644
--- a/glutin/src/application.rs
+++ b/glutin/src/application.rs
@@ -47,6 +47,8 @@ pub fn run<A, E, C>(
let mut title = application.title();
let mut mode = application.mode();
+ let mut background_color = application.background_color();
+ let mut scale_factor = application.scale_factor();
let context = {
let builder = settings.window.into_builder(
@@ -68,13 +70,14 @@ pub fn run<A, E, C>(
};
let clipboard = Clipboard::new(&context.window());
+ let mut cursor_position = glutin::dpi::PhysicalPosition::new(-1.0, -1.0);
let mut mouse_interaction = mouse::Interaction::default();
let mut modifiers = glutin::event::ModifiersState::default();
let physical_size = context.window().inner_size();
let mut viewport = Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height),
- context.window().scale_factor(),
+ context.window().scale_factor() * scale_factor,
);
let mut resized = false;
@@ -88,6 +91,7 @@ pub fn run<A, E, C>(
let mut state = program::State::new(
application,
viewport.logical_size(),
+ conversion::cursor_position(cursor_position, viewport.scale_factor()),
&mut renderer,
&mut debug,
);
@@ -95,10 +99,18 @@ pub fn run<A, E, C>(
event_loop.run(move |event, _, control_flow| match event {
event::Event::MainEventsCleared => {
+ if state.is_queue_empty() {
+ return;
+ }
+
let command = runtime.enter(|| {
state.update(
- clipboard.as_ref().map(|c| c as _),
viewport.logical_size(),
+ conversion::cursor_position(
+ cursor_position,
+ viewport.scale_factor(),
+ ),
+ clipboard.as_ref().map(|c| c as _),
&mut renderer,
&mut debug,
)
@@ -134,6 +146,39 @@ pub fn run<A, E, C>(
mode = new_mode;
}
+
+ // Update background color
+ background_color = program.background_color();
+
+ // Update scale factor
+ let new_scale_factor = program.scale_factor();
+
+ if scale_factor != new_scale_factor {
+ let size = context.window().inner_size();
+
+ viewport = Viewport::with_physical_size(
+ Size::new(size.width, size.height),
+ context.window().scale_factor() * new_scale_factor,
+ );
+
+ // We relayout the UI with the new logical size.
+ // The queue is empty, therefore this will never produce
+ // a `Command`.
+ //
+ // TODO: Properly queue `WindowResized`
+ let _ = state.update(
+ viewport.logical_size(),
+ conversion::cursor_position(
+ cursor_position,
+ viewport.scale_factor(),
+ ),
+ clipboard.as_ref().map(|c| c as _),
+ &mut renderer,
+ &mut debug,
+ );
+
+ scale_factor = new_scale_factor;
+ }
}
context.window().request_redraw();
@@ -160,6 +205,7 @@ pub fn run<A, E, C>(
let new_mouse_interaction = compositor.draw(
&mut renderer,
&viewport,
+ background_color,
state.primitive(),
&debug.overlay(),
);
@@ -186,7 +232,9 @@ pub fn run<A, E, C>(
application::handle_window_event(
&window_event,
context.window(),
+ scale_factor,
control_flow,
+ &mut cursor_position,
&mut modifiers,
&mut viewport,
&mut resized,
diff --git a/graphics/src/backend.rs b/graphics/src/backend.rs
index 83510311..b73c636e 100644
--- a/graphics/src/backend.rs
+++ b/graphics/src/backend.rs
@@ -25,6 +25,9 @@ pub trait Text {
/// [`ICON_FONT`]: #associatedconst.ICON_FONt
const CHECKMARK_ICON: char;
+ /// Returns the default size of text.
+ fn default_size(&self) -> u16;
+
/// Measures the text contents with the given size and font,
/// returning the size of a laid out paragraph that fits in the provided
/// bounds.
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index b6dda132..38d8dffa 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -9,7 +9,6 @@
#![forbid(rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod antialiasing;
-mod defaults;
mod primitive;
mod renderer;
mod transformation;
@@ -17,6 +16,7 @@ mod viewport;
mod widget;
pub mod backend;
+pub mod defaults;
pub mod font;
pub mod layer;
pub mod triangle;
@@ -35,6 +35,6 @@ pub use transformation::Transformation;
pub use viewport::Viewport;
pub use iced_native::{
- Background, Font, HorizontalAlignment, Point, Rectangle, Size, Vector,
- VerticalAlignment,
+ Background, Color, Font, HorizontalAlignment, Point, Rectangle, Size,
+ Vector, VerticalAlignment,
};
diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs
index aeb862d5..ecabc868 100644
--- a/graphics/src/widget/button.rs
+++ b/graphics/src/widget/button.rs
@@ -103,7 +103,7 @@ where
} else {
content
},
- if is_mouse_over {
+ if is_mouse_over && !is_disabled {
mouse::Interaction::Pointer
} else {
mouse::Interaction::default()
diff --git a/graphics/src/widget/canvas/path/builder.rs b/graphics/src/widget/canvas/path/builder.rs
index 6511fa52..e0e52845 100644
--- a/graphics/src/widget/canvas/path/builder.rs
+++ b/graphics/src/widget/canvas/path/builder.rs
@@ -84,7 +84,7 @@ impl Builder {
radii: math::Vector::new(arc.radii.x, arc.radii.y),
x_rotation: math::Angle::radians(arc.rotation),
start_angle: math::Angle::radians(arc.start_angle),
- sweep_angle: math::Angle::radians(arc.end_angle),
+ sweep_angle: math::Angle::radians(arc.end_angle - arc.start_angle),
};
let _ = self.raw.move_to(arc.sample(0.0));
diff --git a/graphics/src/widget/slider.rs b/graphics/src/widget/slider.rs
index b00cde9a..da8b5a86 100644
--- a/graphics/src/widget/slider.rs
+++ b/graphics/src/widget/slider.rs
@@ -16,8 +16,8 @@ pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
/// values.
///
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
-pub type Slider<'a, Message, Backend> =
- iced_native::Slider<'a, Message, Renderer<Backend>>;
+pub type Slider<'a, T, Message, Backend> =
+ iced_native::Slider<'a, T, Message, Renderer<Backend>>;
const HANDLE_HEIGHT: f32 = 22.0;
diff --git a/graphics/src/widget/text.rs b/graphics/src/widget/text.rs
index 327f8e29..7e22e680 100644
--- a/graphics/src/widget/text.rs
+++ b/graphics/src/widget/text.rs
@@ -20,7 +20,9 @@ where
{
type Font = Font;
- const DEFAULT_SIZE: u16 = 20;
+ fn default_size(&self) -> u16 {
+ self.backend().default_size()
+ }
fn measure(
&self,
diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs
index f13f6606..575d67f5 100644
--- a/graphics/src/widget/text_input.rs
+++ b/graphics/src/widget/text_input.rs
@@ -27,14 +27,8 @@ impl<B> text_input::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
- type Font = Font;
type Style = Box<dyn StyleSheet>;
- fn default_size(&self) -> u16 {
- // TODO: Make this configurable
- 20
- }
-
fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 {
let backend = self.backend();
diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs
index d5920c95..aa625f43 100644
--- a/graphics/src/window/compositor.rs
+++ b/graphics/src/window/compositor.rs
@@ -1,4 +1,4 @@
-use crate::Viewport;
+use crate::{Color, Viewport};
use iced_native::mouse;
use raw_window_handle::HasRawWindowHandle;
@@ -49,6 +49,7 @@ pub trait Compositor: Sized {
renderer: &mut Self::Renderer,
swap_chain: &mut Self::SwapChain,
viewport: &Viewport,
+ background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction;
diff --git a/graphics/src/window/gl_compositor.rs b/graphics/src/window/gl_compositor.rs
index 542213b5..2ba39d6e 100644
--- a/graphics/src/window/gl_compositor.rs
+++ b/graphics/src/window/gl_compositor.rs
@@ -1,4 +1,4 @@
-use crate::{Size, Viewport};
+use crate::{Color, Size, Viewport};
use iced_native::mouse;
use core::ffi::c_void;
@@ -61,6 +61,7 @@ pub trait GLCompositor: Sized {
&mut self,
renderer: &mut Self::Renderer,
viewport: &Viewport,
+ background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction;
diff --git a/native/Cargo.toml b/native/Cargo.toml
index 75b4a56b..13052a93 100644
--- a/native/Cargo.toml
+++ b/native/Cargo.toml
@@ -13,6 +13,7 @@ debug = []
[dependencies]
twox-hash = "1.5"
unicode-segmentation = "1.6"
+num-traits = "0.2"
[dependencies.iced_core]
version = "0.2"
diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs
index 5338d0d9..8a712038 100644
--- a/native/src/debug/basic.rs
+++ b/native/src/debug/basic.rs
@@ -174,9 +174,13 @@ impl Debug {
lines.push(key_value("Render:", self.render_durations.average()));
lines.push(key_value("Message count:", self.message_count));
lines.push(String::from("Last messages:"));
- lines.extend(
- self.last_messages.iter().map(|msg| format!(" {}", msg)),
- );
+ lines.extend(self.last_messages.iter().map(|msg| {
+ if msg.len() <= 100 {
+ format!(" {}", msg)
+ } else {
+ format!(" {:.100}...", msg)
+ }
+ }));
lines
}
diff --git a/native/src/program/state.rs b/native/src/program/state.rs
index 8716d8b9..fdc42e8b 100644
--- a/native/src/program/state.rs
+++ b/native/src/program/state.rs
@@ -1,5 +1,5 @@
use crate::{
- Cache, Clipboard, Command, Debug, Event, Program, Renderer, Size,
+ Cache, Clipboard, Command, Debug, Event, Point, Program, Renderer, Size,
UserInterface,
};
@@ -31,6 +31,7 @@ where
pub fn new(
mut program: P,
bounds: Size,
+ cursor_position: Point,
renderer: &mut P::Renderer,
debug: &mut Debug,
) -> Self {
@@ -43,7 +44,7 @@ where
);
debug.draw_started();
- let primitive = user_interface.draw(renderer);
+ let primitive = user_interface.draw(renderer, cursor_position);
debug.draw_finished();
let cache = Some(user_interface.into_cache());
@@ -88,6 +89,13 @@ where
self.queued_messages.push(message);
}
+ /// Returns whether the event queue of the [`State`] is empty or not.
+ ///
+ /// [`State`]: struct.State.html
+ pub fn is_queue_empty(&self) -> bool {
+ self.queued_events.is_empty() && self.queued_messages.is_empty()
+ }
+
/// Processes all the queued events and messages, rebuilding and redrawing
/// the widgets of the linked [`Program`] if necessary.
///
@@ -97,15 +105,12 @@ where
/// [`Program`]: trait.Program.html
pub fn update(
&mut self,
- clipboard: Option<&dyn Clipboard>,
bounds: Size,
+ cursor_position: Point,
+ clipboard: Option<&dyn Clipboard>,
renderer: &mut P::Renderer,
debug: &mut Debug,
) -> Option<Command<P::Message>> {
- if self.queued_events.is_empty() && self.queued_messages.is_empty() {
- return None;
- }
-
let mut user_interface = build_user_interface(
&mut self.program,
self.cache.take().unwrap(),
@@ -117,6 +122,7 @@ where
debug.event_processing_started();
let mut messages = user_interface.update(
self.queued_events.drain(..),
+ cursor_position,
clipboard,
renderer,
);
@@ -125,7 +131,7 @@ where
if messages.is_empty() {
debug.draw_started();
- self.primitive = user_interface.draw(renderer);
+ self.primitive = user_interface.draw(renderer, cursor_position);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
@@ -156,7 +162,7 @@ where
);
debug.draw_started();
- self.primitive = user_interface.draw(renderer);
+ self.primitive = user_interface.draw(renderer, cursor_position);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs
index d6a8e18f..580f58f8 100644
--- a/native/src/renderer/null.rs
+++ b/native/src/renderer/null.rs
@@ -50,7 +50,9 @@ impl row::Renderer for Null {
impl text::Renderer for Null {
type Font = Font;
- const DEFAULT_SIZE: u16 = 20;
+ fn default_size(&self) -> u16 {
+ 20
+ }
fn measure(
&self,
@@ -104,13 +106,8 @@ impl scrollable::Renderer for Null {
}
impl text_input::Renderer for Null {
- type Font = Font;
type Style = ();
- fn default_size(&self) -> u16 {
- 20
- }
-
fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 {
0.0
}
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index e963b601..b9646043 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -1,4 +1,4 @@
-use crate::{layout, mouse, Clipboard, Element, Event, Layout, Point, Size};
+use crate::{layout, Clipboard, Element, Event, Layout, Point, Size};
use std::hash::Hasher;
@@ -23,7 +23,6 @@ pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
layout: layout::Node,
bounds: Size,
- cursor_position: Point,
}
impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer>
@@ -115,7 +114,6 @@ where
root,
layout,
bounds,
- cursor_position: cache.cursor_position,
}
}
@@ -132,7 +130,7 @@ where
/// completing [the previous example](#example):
///
/// ```no_run
- /// use iced_native::{UserInterface, Cache, Size};
+ /// use iced_native::{UserInterface, Cache, Size, Point};
/// use iced_wgpu::Renderer;
///
/// # mod iced_wgpu {
@@ -154,6 +152,7 @@ where
/// let mut cache = Cache::new();
/// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0);
+ /// let mut cursor_position = Point::default();
///
/// // Initialize our event storage
/// let mut events = Vec::new();
@@ -169,7 +168,12 @@ where
/// );
///
/// // Update the user interface
- /// let messages = user_interface.update(events.drain(..), None, &renderer);
+ /// let messages = user_interface.update(
+ /// events.drain(..),
+ /// cursor_position,
+ /// None,
+ /// &renderer,
+ /// );
///
/// cache = user_interface.into_cache();
///
@@ -182,20 +186,17 @@ where
pub fn update(
&mut self,
events: impl IntoIterator<Item = Event>,
+ cursor_position: Point,
clipboard: Option<&dyn Clipboard>,
renderer: &Renderer,
) -> Vec<Message> {
let mut messages = Vec::new();
for event in events {
- if let Event::Mouse(mouse::Event::CursorMoved { x, y }) = event {
- self.cursor_position = Point::new(x, y);
- }
-
self.root.widget.on_event(
event,
Layout::new(&self.layout),
- self.cursor_position,
+ cursor_position,
&mut messages,
renderer,
clipboard,
@@ -219,7 +220,7 @@ where
/// [completing the last example](#example-1):
///
/// ```no_run
- /// use iced_native::{UserInterface, Cache, Size};
+ /// use iced_native::{UserInterface, Cache, Size, Point};
/// use iced_wgpu::Renderer;
///
/// # mod iced_wgpu {
@@ -241,6 +242,7 @@ where
/// let mut cache = Cache::new();
/// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0);
+ /// let mut cursor_position = Point::default();
/// let mut events = Vec::new();
///
/// loop {
@@ -253,10 +255,15 @@ where
/// &mut renderer,
/// );
///
- /// let messages = user_interface.update(events.drain(..), None, &renderer);
+ /// let messages = user_interface.update(
+ /// events.drain(..),
+ /// cursor_position,
+ /// None,
+ /// &renderer,
+ /// );
///
/// // Draw the user interface
- /// let mouse_cursor = user_interface.draw(&mut renderer);
+ /// let mouse_cursor = user_interface.draw(&mut renderer, cursor_position);
///
/// cache = user_interface.into_cache();
///
@@ -268,12 +275,16 @@ where
/// // Flush rendering operations...
/// }
/// ```
- pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Output {
+ pub fn draw(
+ &self,
+ renderer: &mut Renderer,
+ cursor_position: Point,
+ ) -> Renderer::Output {
self.root.widget.draw(
renderer,
&Renderer::Defaults::default(),
Layout::new(&self.layout),
- self.cursor_position,
+ cursor_position,
)
}
@@ -287,7 +298,6 @@ where
hash: self.hash,
layout: self.layout,
bounds: self.bounds,
- cursor_position: self.cursor_position,
}
}
}
@@ -300,7 +310,6 @@ pub struct Cache {
hash: u64,
layout: layout::Node,
bounds: Size,
- cursor_position: Point,
}
impl Cache {
@@ -316,7 +325,6 @@ impl Cache {
hash: 0,
layout: layout::Node::new(Size::new(0.0, 0.0)),
bounds: Size::ZERO,
- cursor_position: Point::new(-1.0, -1.0),
}
}
}
@@ -329,7 +337,7 @@ impl Default for Cache {
impl PartialEq for Cache {
fn eq(&self, other: &Cache) -> bool {
- self.hash == other.hash && self.cursor_position == other.cursor_position
+ self.hash == other.hash
}
}
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 5fb13290..44962288 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -32,7 +32,7 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
width: Length,
size: u16,
spacing: u16,
- text_size: u16,
+ text_size: Option<u16>,
style: Renderer::Style,
}
@@ -60,7 +60,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
width: Length::Shrink,
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
spacing: Renderer::DEFAULT_SPACING,
- text_size: <Renderer as text::Renderer>::DEFAULT_SIZE,
+ text_size: None,
style: Renderer::Style::default(),
}
}
@@ -93,7 +93,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
///
/// [`Checkbox`]: struct.Checkbox.html
pub fn text_size(mut self, text_size: u16) -> Self {
- self.text_size = text_size;
+ self.text_size = Some(text_size);
self
}
@@ -136,7 +136,7 @@ where
.push(
Text::new(&self.label)
.width(self.width)
- .size(self.text_size),
+ .size(self.text_size.unwrap_or(renderer.default_size())),
)
.layout(renderer, limits)
}
@@ -181,7 +181,7 @@ where
defaults,
label_layout.bounds(),
&self.label,
- self.text_size,
+ self.text_size.unwrap_or(renderer.default_size()),
Default::default(),
None,
HorizontalAlignment::Left,
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index a3282ed7..21788e6a 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/pane_grid.rs
@@ -216,6 +216,10 @@ where
/// The `leeway` describes the amount of space around a split that can be
/// used to grab it.
///
+ /// The grabbable area of a split will have a length of `spacing + leeway`,
+ /// properly centered. In other words, a length of
+ /// `(spacing + leeway) / 2.0` on either side of the split line.
+ ///
/// [`PaneGrid`]: struct.PaneGrid.html
pub fn on_resize<F>(mut self, leeway: u16, f: F) -> Self
where
@@ -284,8 +288,6 @@ where
self.state.focus(pane);
}
}
- } else {
- self.state.unfocus();
}
}
@@ -299,7 +301,7 @@ where
if let Some((split, _)) = self.state.picked_split() {
let bounds = layout.bounds();
- let splits = self.state.splits(
+ let splits = self.state.split_regions(
f32::from(self.spacing),
Size::new(bounds.width, bounds.height),
);
@@ -421,7 +423,7 @@ where
let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO);
- let regions = self.state.regions(f32::from(self.spacing), size);
+ let regions = self.state.pane_regions(f32::from(self.spacing), size);
let children = self
.elements
@@ -464,7 +466,7 @@ where
cursor_position.y - bounds.y,
);
- let splits = self.state.splits(
+ let splits = self.state.split_regions(
f32::from(self.spacing),
Size::new(bounds.width, bounds.height),
);
@@ -493,6 +495,8 @@ where
);
}
}
+ } else {
+ self.state.unfocus();
}
}
mouse::Event::ButtonReleased(mouse::Button::Left) => {
@@ -550,10 +554,8 @@ where
}
}
}
-
- *self.pressed_modifiers = modifiers;
}
- keyboard::Event::KeyReleased { modifiers, .. } => {
+ keyboard::Event::ModifiersChanged(modifiers) => {
*self.pressed_modifiers = modifiers;
}
_ => {}
@@ -601,7 +603,7 @@ where
let splits = self
.state
- .splits(f32::from(self.spacing), bounds.size());
+ .split_regions(f32::from(self.spacing), bounds.size());
hovered_split(
splits.iter(),
diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs
index b13c5e26..cbfd8a43 100644
--- a/native/src/widget/pane_grid/node.rs
+++ b/native/src/widget/pane_grid/node.rs
@@ -43,12 +43,36 @@ pub enum Node {
}
impl Node {
+ /// Returns an iterator over each [`Split`] in this [`Node`].
+ ///
+ /// [`Split`]: struct.Split.html
+ /// [`Node`]: enum.Node.html
+ pub fn splits(&self) -> impl Iterator<Item = &Split> {
+ let mut unvisited_nodes = vec![self];
+
+ std::iter::from_fn(move || {
+ while let Some(node) = unvisited_nodes.pop() {
+ match node {
+ Node::Split { id, a, b, .. } => {
+ unvisited_nodes.push(a);
+ unvisited_nodes.push(b);
+
+ return Some(id);
+ }
+ _ => {}
+ }
+ }
+
+ None
+ })
+ }
+
/// Returns the rectangular region for each [`Pane`] in the [`Node`] given
/// the spacing between panes and the total available space.
///
/// [`Pane`]: struct.Pane.html
/// [`Node`]: enum.Node.html
- pub fn regions(
+ pub fn pane_regions(
&self,
spacing: f32,
size: Size,
@@ -75,7 +99,7 @@ impl Node {
///
/// [`Split`]: struct.Split.html
/// [`Node`]: enum.Node.html
- pub fn splits(
+ pub fn split_regions(
&self,
spacing: f32,
size: Size,
diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs
index a8431dec..3f0accc1 100644
--- a/native/src/widget/pane_grid/state.rs
+++ b/native/src/widget/pane_grid/state.rs
@@ -157,8 +157,10 @@ impl<T> State<T> {
/// [`Pane`]: struct.Pane.html
/// [`State::active`]: struct.State.html#method.active
pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> {
- let regions =
- self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0));
+ let regions = self
+ .internal
+ .layout
+ .pane_regions(0.0, Size::new(4096.0, 4096.0));
let current_region = regions.get(pane)?;
@@ -194,6 +196,13 @@ impl<T> State<T> {
self.internal.focus(pane);
}
+ /// Unfocuses the current focused [`Pane`].
+ ///
+ /// [`Pane`]: struct.Pane.html
+ pub fn unfocus(&mut self) {
+ self.internal.unfocus();
+ }
+
/// Splits the given [`Pane`] into two in the given [`Axis`] and
/// initializing the new [`Pane`] with the provided internal state.
///
@@ -366,20 +375,20 @@ impl Internal {
}
}
- pub fn regions(
+ pub fn pane_regions(
&self,
spacing: f32,
size: Size,
) -> HashMap<Pane, Rectangle> {
- self.layout.regions(spacing, size)
+ self.layout.pane_regions(spacing, size)
}
- pub fn splits(
+ pub fn split_regions(
&self,
spacing: f32,
size: Size,
) -> HashMap<Split, (Axis, Rectangle, f32)> {
- self.layout.splits(spacing, size)
+ self.layout.split_regions(spacing, size)
}
pub fn focus(&mut self, pane: &Pane) {
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index d07a9012..5b8d00e9 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -41,7 +41,7 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
width: Length,
size: u16,
spacing: u16,
- text_size: u16,
+ text_size: Option<u16>,
style: Renderer::Style,
}
@@ -75,7 +75,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
width: Length::Shrink,
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
spacing: Renderer::DEFAULT_SPACING, //15
- text_size: <Renderer as text::Renderer>::DEFAULT_SIZE,
+ text_size: None,
style: Renderer::Style::default(),
}
}
@@ -108,7 +108,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
///
/// [`Radio`]: struct.Radio.html
pub fn text_size(mut self, text_size: u16) -> Self {
- self.text_size = text_size;
+ self.text_size = Some(text_size);
self
}
@@ -151,7 +151,7 @@ where
.push(
Text::new(&self.label)
.width(self.width)
- .size(self.text_size),
+ .size(self.text_size.unwrap_or(renderer.default_size())),
)
.layout(renderer, limits)
}
@@ -194,7 +194,7 @@ where
defaults,
label_layout.bounds(),
&self.label,
- self.text_size,
+ self.text_size.unwrap_or(renderer.default_size()),
Default::default(),
None,
HorizontalAlignment::Left,
diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs
index 753a49fe..70f2b6ac 100644
--- a/native/src/widget/slider.rs
+++ b/native/src/widget/slider.rs
@@ -16,13 +16,16 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// A [`Slider`] will try to fill the horizontal space of its container.
///
+/// The [`Slider`] range of numeric values is generic and its step size defaults
+/// to 1 unit.
+///
/// [`Slider`]: struct.Slider.html
///
/// # Example
/// ```
/// # use iced_native::{slider, renderer::Null};
/// #
-/// # pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Null>;
+/// # pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Null>;
/// pub enum Message {
/// SliderChanged(f32),
/// }
@@ -35,17 +38,22 @@ use std::{hash::Hash, ops::RangeInclusive};
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Slider<'a, Message, Renderer: self::Renderer> {
+pub struct Slider<'a, T, Message, Renderer: self::Renderer> {
state: &'a mut State,
- range: RangeInclusive<f32>,
- value: f32,
- on_change: Box<dyn Fn(f32) -> Message>,
+ range: RangeInclusive<T>,
+ step: T,
+ value: T,
+ on_change: Box<dyn Fn(T) -> Message>,
on_release: Option<Message>,
width: Length,
style: Renderer::Style,
}
-impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
+impl<'a, T, Message, Renderer> Slider<'a, T, Message, Renderer>
+where
+ T: Copy + From<u8> + std::cmp::PartialOrd,
+ Renderer: self::Renderer,
+{
/// Creates a new [`Slider`].
///
/// It expects:
@@ -60,17 +68,30 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
/// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
- range: RangeInclusive<f32>,
- value: f32,
+ range: RangeInclusive<T>,
+ value: T,
on_change: F,
) -> Self
where
- F: 'static + Fn(f32) -> Message,
+ F: 'static + Fn(T) -> Message,
{
+ let value = if value >= *range.start() {
+ value
+ } else {
+ *range.start()
+ };
+
+ let value = if value <= *range.end() {
+ value
+ } else {
+ *range.end()
+ };
+
Slider {
state,
- value: value.max(*range.start()).min(*range.end()),
+ value,
range,
+ step: T::from(1),
on_change: Box::new(on_change),
on_release: None,
width: Length::Fill,
@@ -106,6 +127,14 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
self.style = style.into();
self
}
+
+ /// Sets the step size of the [`Slider`].
+ ///
+ /// [`Slider`]: struct.Slider.html
+ pub fn step(mut self, step: T) -> Self {
+ self.step = step;
+ self
+ }
}
/// The local state of a [`Slider`].
@@ -125,9 +154,10 @@ impl State {
}
}
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for Slider<'a, Message, Renderer>
+impl<'a, T, Message, Renderer> Widget<Message, Renderer>
+ for Slider<'a, T, Message, Renderer>
where
+ T: Copy + Into<f64> + num_traits::FromPrimitive,
Renderer: self::Renderer,
Message: Clone,
{
@@ -164,17 +194,24 @@ where
) {
let mut change = || {
let bounds = layout.bounds();
-
if cursor_position.x <= bounds.x {
messages.push((self.on_change)(*self.range.start()));
} else if cursor_position.x >= bounds.x + bounds.width {
messages.push((self.on_change)(*self.range.end()));
} else {
- let percent = (cursor_position.x - bounds.x) / bounds.width;
- let value = (self.range.end() - self.range.start()) * percent
- + self.range.start();
+ let step = self.step.into();
+ let start = (*self.range.start()).into();
+ let end = (*self.range.end()).into();
+
+ let percent = f64::from(cursor_position.x - bounds.x)
+ / f64::from(bounds.width);
+
+ let steps = (percent * (end - start) / step).round();
+ let value = steps * step + start;
- messages.push((self.on_change)(value));
+ if let Some(value) = T::from_f64(value) {
+ messages.push((self.on_change)(value));
+ }
}
};
@@ -212,11 +249,14 @@ where
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
+ let start = *self.range.start();
+ let end = *self.range.end();
+
renderer.draw(
layout.bounds(),
cursor_position,
- self.range.clone(),
- self.value,
+ start.into() as f32..=end.into() as f32,
+ self.value.into() as f32,
self.state.is_dragging,
&self.style,
)
@@ -269,14 +309,15 @@ pub trait Renderer: crate::Renderer {
) -> Self::Output;
}
-impl<'a, Message, Renderer> From<Slider<'a, Message, Renderer>>
+impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
for Element<'a, Message, Renderer>
where
+ T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
Renderer: 'a + self::Renderer,
Message: 'a + Clone,
{
fn from(
- slider: Slider<'a, Message, Renderer>,
+ slider: Slider<'a, T, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(slider)
}
diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs
index 0b05b67d..48a69e34 100644
--- a/native/src/widget/text.rs
+++ b/native/src/widget/text.rs
@@ -131,7 +131,7 @@ where
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
- let size = self.size.unwrap_or(Renderer::DEFAULT_SIZE);
+ let size = self.size.unwrap_or(renderer.default_size());
let bounds = limits.max();
@@ -154,7 +154,7 @@ where
defaults,
layout.bounds(),
&self.content,
- self.size.unwrap_or(Renderer::DEFAULT_SIZE),
+ self.size.unwrap_or(renderer.default_size()),
self.font,
self.color,
self.horizontal_alignment,
@@ -187,10 +187,10 @@ pub trait Renderer: crate::Renderer {
/// [`Text`]: struct.Text.html
type Font: Default + Copy;
- /// The default size of [`Text`].
+ /// Returns the default size of [`Text`].
///
/// [`Text`]: struct.Text.html
- const DEFAULT_SIZE: u16;
+ fn default_size(&self) -> u16;
/// Measures the [`Text`] in the given bounds and returns the minimum
/// boundaries that can fit the contents.
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index e3a5355b..3f415101 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -17,8 +17,8 @@ use editor::Editor;
use crate::{
keyboard, layout,
mouse::{self, click},
- Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size,
- Widget,
+ text, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle,
+ Size, Widget,
};
use std::u32;
@@ -486,7 +486,8 @@ where
let text_bounds = layout.children().next().unwrap().bounds();
if self.is_secure {
- renderer.draw(
+ self::Renderer::draw(
+ renderer,
bounds,
text_bounds,
cursor_position,
@@ -498,7 +499,8 @@ where
&self.style,
)
} else {
- renderer.draw(
+ self::Renderer::draw(
+ renderer,
bounds,
text_bounds,
cursor_position,
@@ -531,20 +533,10 @@ where
///
/// [`TextInput`]: struct.TextInput.html
/// [renderer]: ../../renderer/index.html
-pub trait Renderer: crate::Renderer + Sized {
- /// The font type used for [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
- type Font: Default + Copy;
-
+pub trait Renderer: text::Renderer + Sized {
/// The style supported by this renderer.
type Style: Default;
- /// Returns the default size of the text of the [`TextInput`].
- ///
- /// [`TextInput`]: struct.TextInput.html
- fn default_size(&self) -> u16;
-
/// Returns the width of the value of the [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
@@ -683,6 +675,30 @@ impl State {
pub fn cursor(&self) -> Cursor {
self.cursor
}
+
+ /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
+ ///
+ /// [`Cursor`]: struct.Cursor.html
+ /// [`TextInput`]: struct.TextInput.html
+ pub fn move_cursor_to_front(&mut self) {
+ self.cursor.move_to(0);
+ }
+
+ /// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text.
+ ///
+ /// [`Cursor`]: struct.Cursor.html
+ /// [`TextInput`]: struct.TextInput.html
+ pub fn move_cursor_to_end(&mut self) {
+ self.cursor.move_to(usize::MAX);
+ }
+
+ /// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location.
+ ///
+ /// [`Cursor`]: struct.Cursor.html
+ /// [`TextInput`]: struct.TextInput.html
+ pub fn move_cursor_to(&mut self, position: usize) {
+ self.cursor.move_to(position);
+ }
}
// TODO: Reduce allocations
diff --git a/src/application.rs b/src/application.rs
index 19cab7da..47b89dbd 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -1,4 +1,6 @@
-use crate::{window, Command, Element, Executor, Settings, Subscription};
+use crate::{
+ window, Color, Command, Element, Executor, Settings, Subscription,
+};
/// An interactive cross-platform application.
///
@@ -174,6 +176,31 @@ pub trait Application: Sized {
window::Mode::Windowed
}
+ /// Returns the background color of the [`Application`].
+ ///
+ /// By default, it returns [`Color::WHITE`].
+ ///
+ /// [`Application`]: trait.Application.html
+ /// [`Color::WHITE`]: struct.Color.html#const.WHITE
+ fn background_color(&self) -> Color {
+ Color::WHITE
+ }
+
+ /// Returns the scale factor of the [`Application`].
+ ///
+ /// It can be used to dynamically control the size of the UI at runtime
+ /// (i.e. zooming).
+ ///
+ /// For instance, a scale factor of `2.0` will make widgets twice as big,
+ /// while a scale factor of `0.5` will shrink them to half their size.
+ ///
+ /// By default, it returns `1.0`.
+ ///
+ /// [`Application`]: trait.Application.html
+ fn scale_factor(&self) -> f64 {
+ 1.0
+ }
+
/// Runs the [`Application`].
///
/// On native platforms, this method will take control of the current thread
@@ -190,6 +217,7 @@ pub trait Application: Sized {
{
let renderer_settings = crate::renderer::Settings {
default_font: settings.default_font,
+ default_text_size: settings.default_text_size,
antialiasing: if settings.antialiasing {
Some(crate::renderer::settings::Antialiasing::MSAAx4)
} else {
@@ -256,6 +284,14 @@ where
fn subscription(&self) -> Subscription<Self::Message> {
self.0.subscription()
}
+
+ fn background_color(&self) -> Color {
+ self.0.background_color()
+ }
+
+ fn scale_factor(&self) -> f64 {
+ self.0.scale_factor()
+ }
}
#[cfg(target_arch = "wasm32")]
diff --git a/src/sandbox.rs b/src/sandbox.rs
index c6fa45d0..6a73eab0 100644
--- a/src/sandbox.rs
+++ b/src/sandbox.rs
@@ -1,4 +1,6 @@
-use crate::{executor, Application, Command, Element, Settings, Subscription};
+use crate::{
+ executor, Application, Color, Command, Element, Settings, Subscription,
+};
/// A sandboxed [`Application`].
///
@@ -124,6 +126,31 @@ pub trait Sandbox {
/// [`Sandbox`]: trait.Sandbox.html
fn view(&mut self) -> Element<'_, Self::Message>;
+ /// Returns the background color of the [`Sandbox`].
+ ///
+ /// By default, it returns [`Color::WHITE`].
+ ///
+ /// [`Sandbox`]: trait.Sandbox.html
+ /// [`Color::WHITE`]: struct.Color.html#const.WHITE
+ fn background_color(&self) -> Color {
+ Color::WHITE
+ }
+
+ /// Returns the scale factor of the [`Sandbox`].
+ ///
+ /// It can be used to dynamically control the size of the UI at runtime
+ /// (i.e. zooming).
+ ///
+ /// For instance, a scale factor of `2.0` will make widgets twice as big,
+ /// while a scale factor of `0.5` will shrink them to half their size.
+ ///
+ /// By default, it returns `1.0`.
+ ///
+ /// [`Sandbox`]: trait.Sandbox.html
+ fn scale_factor(&self) -> f64 {
+ 1.0
+ }
+
/// Runs the [`Sandbox`].
///
/// On native platforms, this method will take control of the current thread
@@ -169,4 +196,12 @@ where
fn view(&mut self) -> Element<'_, T::Message> {
T::view(self)
}
+
+ fn background_color(&self) -> Color {
+ T::background_color(self)
+ }
+
+ fn scale_factor(&self) -> f64 {
+ T::scale_factor(self)
+ }
}
diff --git a/src/settings.rs b/src/settings.rs
index 01ad0ee0..40b1b1ea 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -2,7 +2,7 @@
use crate::window;
/// The settings of an application.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+#[derive(Debug, Clone)]
pub struct Settings<Flags> {
/// The window settings.
///
@@ -22,6 +22,11 @@ pub struct Settings<Flags> {
// TODO: Add `name` for web compatibility
pub default_font: Option<&'static [u8]>,
+ /// The text size that will be used by default.
+ ///
+ /// The default value is 20.
+ pub default_text_size: u16,
+
/// If set to true, the renderer will try to perform antialiasing for some
/// primitives.
///
@@ -39,12 +44,28 @@ impl<Flags> Settings<Flags> {
///
/// [`Application`]: ../trait.Application.html
pub fn with_flags(flags: Flags) -> Self {
+ let default_settings = Settings::<()>::default();
+
Self {
flags,
- // not using ..Default::default() struct update syntax since it is more permissive to
- // allow initializing with flags without trait bound on Default
+ antialiasing: default_settings.antialiasing,
+ default_font: default_settings.default_font,
+ default_text_size: default_settings.default_text_size,
+ window: default_settings.window,
+ }
+ }
+}
+
+impl<Flags> Default for Settings<Flags>
+where
+ Flags: Default,
+{
+ fn default() -> Self {
+ Self {
+ flags: Default::default(),
antialiasing: Default::default(),
default_font: Default::default(),
+ default_text_size: 20,
window: Default::default(),
}
}
@@ -54,12 +75,7 @@ impl<Flags> Settings<Flags> {
impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> {
fn from(settings: Settings<Flags>) -> iced_winit::Settings<Flags> {
iced_winit::Settings {
- window: iced_winit::settings::Window {
- size: settings.window.size,
- resizable: settings.window.resizable,
- decorations: settings.window.decorations,
- platform_specific: Default::default(),
- },
+ window: settings.window.into(),
flags: settings.flags,
}
}
diff --git a/src/widget.rs b/src/widget.rs
index 3e4d4788..007bd531 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -50,7 +50,7 @@ mod platform {
text_input::TextInput,
};
- #[cfg(feature = "canvas")]
+ #[cfg(any(feature = "canvas", feature = "glow_canvas"))]
#[doc(no_inline)]
pub use canvas::Canvas;
}
diff --git a/src/window.rs b/src/window.rs
index 54ea2a02..a2883b62 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -2,5 +2,8 @@
mod mode;
mod settings;
+pub mod icon;
+
+pub use icon::Icon;
pub use mode::Mode;
pub use settings::Settings;
diff --git a/src/window/icon.rs b/src/window/icon.rs
new file mode 100644
index 00000000..15e0312d
--- /dev/null
+++ b/src/window/icon.rs
@@ -0,0 +1,132 @@
+//! Attach an icon to the window of your application.
+use std::fmt;
+use std::io;
+
+/// The icon of a window.
+#[cfg(not(target_arch = "wasm32"))]
+#[derive(Debug, Clone)]
+pub struct Icon(iced_winit::winit::window::Icon);
+
+/// The icon of a window.
+#[cfg(target_arch = "wasm32")]
+#[derive(Debug, Clone)]
+pub struct Icon;
+
+impl Icon {
+ /// Creates an icon from 32bpp RGBA data.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub fn from_rgba(
+ rgba: Vec<u8>,
+ width: u32,
+ height: u32,
+ ) -> Result<Self, Error> {
+ let raw =
+ iced_winit::winit::window::Icon::from_rgba(rgba, width, height)?;
+
+ Ok(Icon(raw))
+ }
+
+ /// Creates an icon from 32bpp RGBA data.
+ #[cfg(target_arch = "wasm32")]
+ pub fn from_rgba(
+ _rgba: Vec<u8>,
+ _width: u32,
+ _height: u32,
+ ) -> Result<Self, Error> {
+ Ok(Icon)
+ }
+}
+
+/// An error produced when using `Icon::from_rgba` with invalid arguments.
+#[derive(Debug)]
+pub enum Error {
+ /// The provided RGBA data isn't divisble by 4.
+ ///
+ /// Therefore, it cannot be safely interpreted as 32bpp RGBA pixels.
+ InvalidData {
+ /// The length of the provided RGBA data.
+ byte_count: usize,
+ },
+
+ /// The number of RGBA pixels does not match the provided dimensions.
+ DimensionsMismatch {
+ /// The provided width.
+ width: u32,
+ /// The provided height.
+ height: u32,
+ /// The amount of pixels of the provided RGBA data.
+ pixel_count: usize,
+ },
+
+ /// The underlying OS failed to create the icon.
+ OsError(io::Error),
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl From<iced_winit::winit::window::BadIcon> for Error {
+ fn from(error: iced_winit::winit::window::BadIcon) -> Self {
+ use iced_winit::winit::window::BadIcon;
+
+ match error {
+ BadIcon::ByteCountNotDivisibleBy4 { byte_count } => {
+ Error::InvalidData { byte_count }
+ }
+ BadIcon::DimensionsVsPixelCount {
+ width,
+ height,
+ pixel_count,
+ ..
+ } => Error::DimensionsMismatch {
+ width,
+ height,
+ pixel_count,
+ },
+ BadIcon::OsError(os_error) => Error::OsError(os_error),
+ }
+ }
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl From<Icon> for iced_winit::winit::window::Icon {
+ fn from(icon: Icon) -> Self {
+ icon.0
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Error::InvalidData { byte_count } => {
+ write!(f,
+ "The provided RGBA data (with length {:?}) isn't divisble by \
+ 4. Therefore, it cannot be safely interpreted as 32bpp RGBA \
+ pixels.",
+ byte_count,
+ )
+ }
+ Error::DimensionsMismatch {
+ width,
+ height,
+ pixel_count,
+ } => {
+ write!(f,
+ "The number of RGBA pixels ({:?}) does not match the provided \
+ dimensions ({:?}x{:?}).",
+ width, height, pixel_count,
+ )
+ }
+ Error::OsError(e) => write!(
+ f,
+ "The underlying OS failed to create the window \
+ icon: {:?}",
+ e
+ ),
+ }
+ }
+}
+
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ Some(self)
+ }
+}
diff --git a/src/window/settings.rs b/src/window/settings.rs
index a31d2af2..2046f2d9 100644
--- a/src/window/settings.rs
+++ b/src/window/settings.rs
@@ -1,22 +1,51 @@
+use crate::window::Icon;
+
/// The window settings of an application.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Clone)]
pub struct Settings {
- /// The size of the window.
+ /// The initial size of the window.
pub size: (u32, u32),
+ /// The minimum size of the window.
+ pub min_size: Option<(u32, u32)>,
+
+ /// The maximum size of the window.
+ pub max_size: Option<(u32, u32)>,
+
/// Whether the window should be resizable or not.
pub resizable: bool,
/// Whether the window should have a border, a title bar, etc. or not.
pub decorations: bool,
+
+ /// The icon of the window.
+ pub icon: Option<Icon>,
}
impl Default for Settings {
fn default() -> Settings {
Settings {
size: (1024, 768),
+ min_size: None,
+ max_size: None,
resizable: true,
decorations: true,
+ icon: None,
+ }
+ }
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl From<Settings> for iced_winit::settings::Window {
+ fn from(settings: Settings) -> Self {
+ Self {
+ size: settings.size,
+ min_size: settings.min_size,
+ max_size: settings.max_size,
+ resizable: settings.resizable,
+ decorations: settings.decorations,
+ icon: settings.icon.map(Icon::into),
+ platform_specific: Default::default(),
}
}
}
diff --git a/style/src/lib.rs b/style/src/lib.rs
index 2c5977b5..72d83aec 100644
--- a/style/src/lib.rs
+++ b/style/src/lib.rs
@@ -2,6 +2,8 @@
//!
//! It contains a set of styles and stylesheets for most of the built-in
//! widgets.
+pub use iced_core::{Background, Color};
+
pub mod button;
pub mod checkbox;
pub mod container;
diff --git a/web/Cargo.toml b/web/Cargo.toml
index 12d3865e..e03d2b63 100644
--- a/web/Cargo.toml
+++ b/web/Cargo.toml
@@ -15,10 +15,11 @@ categories = ["web-programming"]
maintenance = { status = "actively-developed" }
[dependencies]
-dodrio = "0.1.0"
-wasm-bindgen = "0.2.51"
+dodrio = "0.2"
+wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
url = "2.0"
+num-traits = "0.2"
[dependencies.iced_core]
version = "0.2"
diff --git a/web/src/lib.rs b/web/src/lib.rs
index 53b54b7e..b7970c56 100644
--- a/web/src/lib.rs
+++ b/web/src/lib.rs
@@ -238,28 +238,25 @@ struct Instance<A: Application> {
bus: Bus<A::Message>,
}
-impl<A> dodrio::Render for Instance<A>
+impl<'a, A> dodrio::Render<'a> for Instance<A>
where
A: Application,
{
- fn render<'a, 'bump>(
- &'a self,
- bump: &'bump bumpalo::Bump,
- ) -> dodrio::Node<'bump>
- where
- 'a: 'bump,
- {
+ fn render(
+ &self,
+ context: &mut dodrio::RenderContext<'a>,
+ ) -> dodrio::Node<'a> {
use dodrio::builder::*;
let mut ui = self.application.borrow_mut();
let element = ui.view();
let mut css = Css::new();
- let node = element.widget.node(bump, &self.bus, &mut css);
+ let node = element.widget.node(context.bump, &self.bus, &mut css);
- div(bump)
+ div(context.bump)
.attr("style", "width: 100%; height: 100%")
- .children(vec![css.node(bump), node])
+ .children(vec![css.node(context.bump), node])
.finish()
}
}
diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs
index 3a5afe60..a4bcc33d 100644
--- a/web/src/widget/button.rs
+++ b/web/src/widget/button.rs
@@ -154,16 +154,20 @@ where
},
};
+ let class = {
+ use dodrio::bumpalo::collections::String;
+
+ String::from_str_in(&padding_class, bump).into_bump_str()
+ };
+
let mut node = button(bump)
- .attr(
- "class",
- bumpalo::format!(in bump, "{}", padding_class).into_bump_str(),
- )
+ .attr("class", class)
.attr(
"style",
bumpalo::format!(
in bump,
- "background: {}; border-radius: {}px; width:{}; min-width: {}; color: {}",
+ "background: {}; border-radius: {}px; width:{}; \
+ min-width: {}; color: {}",
background,
style.border_radius,
css::length(self.width),
diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs
index 5ebc26c8..21801e39 100644
--- a/web/src/widget/checkbox.rs
+++ b/web/src/widget/checkbox.rs
@@ -28,6 +28,7 @@ pub struct Checkbox<Message> {
is_checked: bool,
on_toggle: Rc<dyn Fn(bool) -> Message>,
label: String,
+ id: Option<String>,
width: Length,
style: Box<dyn StyleSheet>,
}
@@ -51,6 +52,7 @@ impl<Message> Checkbox<Message> {
is_checked,
on_toggle: Rc::new(f),
label: label.into(),
+ id: None,
width: Length::Shrink,
style: Default::default(),
}
@@ -71,6 +73,14 @@ impl<Message> Checkbox<Message> {
self.style = style.into();
self
}
+
+ /// Sets the id of the [`Checkbox`].
+ ///
+ /// [`Checkbox`]: struct.Checkbox.html
+ pub fn id(mut self, id: impl Into<String>) -> Self {
+ self.id = Some(id.into());
+ self
+ }
}
impl<Message> Widget<Message> for Checkbox<Message>
@@ -84,8 +94,10 @@ where
style_sheet: &mut Css<'b>,
) -> dodrio::Node<'b> {
use dodrio::builder::*;
+ use dodrio::bumpalo::collections::String;
- let checkbox_label = bumpalo::format!(in bump, "{}", self.label);
+ let checkbox_label =
+ String::from_str_in(&self.label, bump).into_bump_str();
let event_bus = bus.clone();
let on_toggle = self.on_toggle.clone();
@@ -95,7 +107,15 @@ where
let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(5));
- label(bump)
+ let (label, input) = if let Some(id) = &self.id {
+ let id = String::from_str_in(id, bump).into_bump_str();
+
+ (label(bump).attr("for", id), input(bump).attr("id", id))
+ } else {
+ (label(bump), input(bump))
+ };
+
+ label
.attr(
"class",
bumpalo::format!(in bump, "{} {}", row_class, spacing_class)
@@ -108,7 +128,7 @@ where
)
.children(vec![
// TODO: Checkbox styling
- input(bump)
+ input
.attr("type", "checkbox")
.bool_attr("checked", self.is_checked)
.on("click", move |_root, vdom, _event| {
@@ -118,8 +138,7 @@ where
vdom.schedule_render();
})
.finish(),
- span(bump).children(vec![
- text(checkbox_label.into_bump_str())]).finish(),
+ text(checkbox_label),
])
.finish()
}
diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs
index 029ab352..a595c29a 100644
--- a/web/src/widget/image.rs
+++ b/web/src/widget/image.rs
@@ -22,6 +22,9 @@ pub struct Image {
/// The image path
pub handle: Handle,
+ /// The alt text of the image
+ pub alt: String,
+
/// The width of the image
pub width: Length,
@@ -36,6 +39,7 @@ impl Image {
pub fn new<T: Into<Handle>>(handle: T) -> Self {
Image {
handle: handle.into(),
+ alt: Default::default(),
width: Length::Shrink,
height: Length::Shrink,
}
@@ -56,6 +60,14 @@ impl Image {
self.height = height;
self
}
+
+ /// Sets the alt text of the [`Image`].
+ ///
+ /// [`Image`]: struct.Image.html
+ pub fn alt(mut self, alt: impl Into<String>) -> Self {
+ self.alt = alt.into();
+ self
+ }
}
impl<Message> Widget<Message> for Image {
@@ -66,12 +78,19 @@ impl<Message> Widget<Message> for Image {
_style_sheet: &mut Css<'b>,
) -> dodrio::Node<'b> {
use dodrio::builder::*;
+ use dodrio::bumpalo::collections::String;
+
+ let src = String::from_str_in(
+ match self.handle.data.as_ref() {
+ Data::Path(path) => path.to_str().unwrap_or(""),
+ },
+ bump,
+ )
+ .into_bump_str();
- let src = bumpalo::format!(in bump, "{}", match self.handle.data.as_ref() {
- Data::Path(path) => path.to_str().unwrap_or("")
- });
+ let alt = String::from_str_in(&self.alt, bump).into_bump_str();
- let mut image = img(bump).attr("src", src.into_bump_str());
+ let mut image = img(bump).attr("src", src).attr("alt", alt);
match self.width {
Length::Shrink => {}
diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs
index 520b24cd..c9d0a00e 100644
--- a/web/src/widget/radio.rs
+++ b/web/src/widget/radio.rs
@@ -35,6 +35,8 @@ pub struct Radio<Message> {
is_selected: bool,
on_click: Message,
label: String,
+ id: Option<String>,
+ name: Option<String>,
style: Box<dyn StyleSheet>,
}
@@ -63,6 +65,8 @@ impl<Message> Radio<Message> {
is_selected: Some(value) == selected,
on_click: f(value),
label: label.into(),
+ id: None,
+ name: None,
style: Default::default(),
}
}
@@ -74,6 +78,22 @@ impl<Message> Radio<Message> {
self.style = style.into();
self
}
+
+ /// Sets the name attribute of the [`Radio`] button.
+ ///
+ /// [`Radio`]: struct.Radio.html
+ pub fn name(mut self, name: impl Into<String>) -> Self {
+ self.name = Some(name.into());
+ self
+ }
+
+ /// Sets the id of the [`Radio`] button.
+ ///
+ /// [`Radio`]: struct.Radio.html
+ pub fn id(mut self, id: impl Into<String>) -> Self {
+ self.id = Some(id.into());
+ self
+ }
}
impl<Message> Widget<Message> for Radio<Message>
@@ -87,17 +107,35 @@ where
_style_sheet: &mut Css<'b>,
) -> dodrio::Node<'b> {
use dodrio::builder::*;
+ use dodrio::bumpalo::collections::String;
- let radio_label = bumpalo::format!(in bump, "{}", self.label);
+ let radio_label =
+ String::from_str_in(&self.label, bump).into_bump_str();
let event_bus = bus.clone();
let on_click = self.on_click.clone();
+ let (label, input) = if let Some(id) = &self.id {
+ let id = String::from_str_in(id, bump).into_bump_str();
+
+ (label(bump).attr("for", id), input(bump).attr("id", id))
+ } else {
+ (label(bump), input(bump))
+ };
+
+ let input = if let Some(name) = &self.name {
+ let name = String::from_str_in(name, bump).into_bump_str();
+
+ dodrio::builder::input(bump).attr("name", name)
+ } else {
+ input
+ };
+
// TODO: Complete styling
- label(bump)
+ label
.attr("style", "display: block; font-size: 20px")
.children(vec![
- input(bump)
+ input
.attr("type", "radio")
.attr("style", "margin-right: 10px")
.bool_attr("checked", self.is_selected)
@@ -105,7 +143,7 @@ where
event_bus.publish(on_click.clone());
})
.finish(),
- text(radio_label.into_bump_str()),
+ text(radio_label),
])
.finish()
}
diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs
index 5aa6439e..a0d9df00 100644
--- a/web/src/widget/slider.rs
+++ b/web/src/widget/slider.rs
@@ -16,6 +16,9 @@ use std::{ops::RangeInclusive, rc::Rc};
///
/// A [`Slider`] will try to fill the horizontal space of its container.
///
+/// The [`Slider`] range of numeric values is generic and its step size defaults
+/// to 1 unit.
+///
/// [`Slider`]: struct.Slider.html
///
/// # Example
@@ -34,16 +37,20 @@ use std::{ops::RangeInclusive, rc::Rc};
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)]
-pub struct Slider<'a, Message> {
+pub struct Slider<'a, T, Message> {
_state: &'a mut State,
- range: RangeInclusive<f32>,
- value: f32,
- on_change: Rc<Box<dyn Fn(f32) -> Message>>,
+ range: RangeInclusive<T>,
+ step: T,
+ value: T,
+ on_change: Rc<Box<dyn Fn(T) -> Message>>,
width: Length,
style: Box<dyn StyleSheet>,
}
-impl<'a, Message> Slider<'a, Message> {
+impl<'a, T, Message> Slider<'a, T, Message>
+where
+ T: Copy + From<u8> + std::cmp::PartialOrd,
+{
/// Creates a new [`Slider`].
///
/// It expects:
@@ -58,17 +65,30 @@ impl<'a, Message> Slider<'a, Message> {
/// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
- range: RangeInclusive<f32>,
- value: f32,
+ range: RangeInclusive<T>,
+ value: T,
on_change: F,
) -> Self
where
- F: 'static + Fn(f32) -> Message,
+ F: 'static + Fn(T) -> Message,
{
+ let value = if value >= *range.start() {
+ value
+ } else {
+ *range.start()
+ };
+
+ let value = if value <= *range.end() {
+ value
+ } else {
+ *range.end()
+ };
+
Slider {
_state: state,
- value: value.max(*range.start()).min(*range.end()),
+ value,
range,
+ step: T::from(1),
on_change: Rc::new(Box::new(on_change)),
width: Length::Fill,
style: Default::default(),
@@ -90,10 +110,19 @@ impl<'a, Message> Slider<'a, Message> {
self.style = style.into();
self
}
+
+ /// Sets the step size of the [`Slider`].
+ ///
+ /// [`Slider`]: struct.Slider.html
+ pub fn step(mut self, step: T) -> Self {
+ self.step = step;
+ self
+ }
}
-impl<'a, Message> Widget<Message> for Slider<'a, Message>
+impl<'a, T, Message> Widget<Message> for Slider<'a, T, Message>
where
+ T: 'static + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'static,
{
fn node<'b>(
@@ -107,18 +136,18 @@ where
let (start, end) = self.range.clone().into_inner();
- let min = bumpalo::format!(in bump, "{}", start);
- let max = bumpalo::format!(in bump, "{}", end);
- let value = bumpalo::format!(in bump, "{}", self.value);
+ let min = bumpalo::format!(in bump, "{}", start.into());
+ let max = bumpalo::format!(in bump, "{}", end.into());
+ let value = bumpalo::format!(in bump, "{}", self.value.into());
+ let step = bumpalo::format!(in bump, "{}", self.step.into());
let on_change = self.on_change.clone();
let event_bus = bus.clone();
- // TODO: Make `step` configurable
// TODO: Styling
input(bump)
.attr("type", "range")
- .attr("step", "0.01")
+ .attr("step", step.into_bump_str())
.attr("min", min.into_bump_str())
.attr("max", max.into_bump_str())
.attr("value", value.into_bump_str())
@@ -131,19 +160,22 @@ where
Some(slider) => slider,
};
- if let Ok(value) = slider.value().parse::<f32>() {
- event_bus.publish(on_change(value));
+ if let Ok(value) = slider.value().parse::<f64>() {
+ if let Some(value) = T::from_f64(value) {
+ event_bus.publish(on_change(value));
+ }
}
})
.finish()
}
}
-impl<'a, Message> From<Slider<'a, Message>> for Element<'a, Message>
+impl<'a, T, Message> From<Slider<'a, T, Message>> for Element<'a, Message>
where
+ T: 'static + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'static,
{
- fn from(slider: Slider<'a, Message>) -> Element<'a, Message> {
+ fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message> {
Element::new(slider)
}
}
diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs
index 3ec565a8..2f7308ee 100644
--- a/web/src/widget/text.rs
+++ b/web/src/widget/text.rs
@@ -116,7 +116,12 @@ impl<'a, Message> Widget<Message> for Text {
) -> dodrio::Node<'b> {
use dodrio::builder::*;
- let content = bumpalo::format!(in bump, "{}", self.content);
+ let content = {
+ use dodrio::bumpalo::collections::String;
+
+ String::from_str_in(&self.content, bump)
+ };
+
let color = self
.color
.map(css::color)
@@ -133,7 +138,8 @@ impl<'a, Message> Widget<Message> for Text {
let style = bumpalo::format!(
in bump,
- "width: {}; height: {}; font-size: {}px; color: {}; text-align: {}; font-family: {}",
+ "width: {}; height: {}; font-size: {}px; color: {}; \
+ text-align: {}; font-family: {}",
width,
height,
self.size.unwrap_or(20),
diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs
index 3fa458bd..0049a553 100644
--- a/web/src/widget/text_input.rs
+++ b/web/src/widget/text_input.rs
@@ -151,8 +151,26 @@ where
use dodrio::builder::*;
use wasm_bindgen::JsCast;
- let padding_class =
- style_sheet.insert(bump, css::Rule::Padding(self.padding));
+ let class = {
+ use dodrio::bumpalo::collections::String;
+
+ let padding_class =
+ style_sheet.insert(bump, css::Rule::Padding(self.padding));
+
+ String::from_str_in(&padding_class, bump).into_bump_str()
+ };
+
+ let placeholder = {
+ use dodrio::bumpalo::collections::String;
+
+ String::from_str_in(&self.placeholder, bump).into_bump_str()
+ };
+
+ let value = {
+ use dodrio::bumpalo::collections::String;
+
+ String::from_str_in(&self.value, bump).into_bump_str()
+ };
let on_change = self.on_change.clone();
let on_submit = self.on_submit.clone();
@@ -161,15 +179,14 @@ where
let style = self.style_sheet.active();
input(bump)
- .attr(
- "class",
- bumpalo::format!(in bump, "{}", padding_class).into_bump_str(),
- )
+ .attr("class", class)
.attr(
"style",
bumpalo::format!(
in bump,
- "width: {}; max-width: {}; font-size: {}px; background: {}; border-width: {}px; border-color: {}; border-radius: {}px; color: {}",
+ "width: {}; max-width: {}; font-size: {}px; \
+ background: {}; border-width: {}px; border-color: {}; \
+ border-radius: {}px; color: {}",
css::length(self.width),
css::max_length(self.max_width),
self.size.unwrap_or(20),
@@ -181,19 +198,9 @@ where
)
.into_bump_str(),
)
- .attr(
- "placeholder",
- bumpalo::format!(in bump, "{}", self.placeholder)
- .into_bump_str(),
- )
- .attr(
- "value",
- bumpalo::format!(in bump, "{}", self.value).into_bump_str(),
- )
- .attr(
- "type",
- bumpalo::format!(in bump, "{}", if self.is_secure { "password" } else { "text" }).into_bump_str(),
- )
+ .attr("placeholder", placeholder)
+ .attr("value", value)
+ .attr("type", if self.is_secure { "password" } else { "text" })
.on("input", move |_root, _vdom, event| {
let text_input = match event.target().and_then(|t| {
t.dyn_into::<web_sys::HtmlInputElement>().ok()
@@ -206,10 +213,13 @@ where
})
.on("keypress", move |_root, _vdom, event| {
if let Some(on_submit) = on_submit.clone() {
- let event = event.unchecked_into::<web_sys::KeyboardEvent>();
+ let event =
+ event.unchecked_into::<web_sys::KeyboardEvent>();
match event.key_code() {
- 13 => { submit_event_bus.publish(on_submit); }
+ 13 => {
+ submit_event_bus.publish(on_submit);
+ }
_ => {}
}
}
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 9ed4438b..a25f42f7 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -24,6 +24,8 @@ pub struct Backend {
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline: image::Pipeline,
+
+ default_text_size: u16,
}
impl Backend {
@@ -50,6 +52,8 @@ impl Backend {
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline,
+
+ default_text_size: settings.default_text_size,
}
}
@@ -245,6 +249,10 @@ impl backend::Text for Backend {
const ICON_FONT: Font = font::ICONS;
const CHECKMARK_ICON: char = font::CHECKMARK_ICON;
+ fn default_size(&self) -> u16 {
+ self.default_text_size
+ }
+
fn measure(
&self,
contents: &str,
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index e67221c7..e51a225c 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -36,7 +36,7 @@ mod backend;
mod quad;
mod text;
-pub use iced_graphics::{Antialiasing, Defaults, Primitive, Viewport};
+pub use iced_graphics::{Antialiasing, Color, Defaults, Primitive, Viewport};
pub use wgpu;
pub use backend::Backend;
diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs
index 4655e64f..bc146c4c 100644
--- a/wgpu/src/settings.rs
+++ b/wgpu/src/settings.rs
@@ -16,6 +16,11 @@ pub struct Settings {
/// If `None` is provided, a default system font will be chosen.
pub default_font: Option<&'static [u8]>,
+ /// The default size of text.
+ ///
+ /// By default, it will be set to 20.
+ pub default_text_size: u16,
+
/// The antialiasing strategy that will be used for triangle primitives.
pub antialiasing: Option<Antialiasing>,
}
@@ -25,6 +30,7 @@ impl Default for Settings {
Settings {
format: wgpu::TextureFormat::Bgra8UnormSrgb,
default_font: None,
+ default_text_size: 20,
antialiasing: None,
}
}
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index fe2388a3..2744c67a 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -16,6 +16,7 @@ const INDEX_BUFFER_SIZE: usize = 10_000;
pub(crate) struct Pipeline {
pipeline: wgpu::RenderPipeline,
blit: Option<msaa::Blit>,
+ constants_layout: wgpu::BindGroupLayout,
constants: wgpu::BindGroup,
uniforms_buffer: Buffer<Uniforms>,
vertex_buffer: Buffer<Vertex2D>,
@@ -50,8 +51,10 @@ impl<T> Buffer<T> {
}
}
- pub fn ensure_capacity(&mut self, device: &wgpu::Device, size: usize) {
- if self.size < size {
+ pub fn expand(&mut self, device: &wgpu::Device, size: usize) -> bool {
+ let needs_resize = self.size < size;
+
+ if needs_resize {
self.raw = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: (std::mem::size_of::<T>() * size) as u64,
@@ -60,6 +63,8 @@ impl<T> Buffer<T> {
self.size = size;
}
+
+ needs_resize
}
}
@@ -69,7 +74,7 @@ impl Pipeline {
format: wgpu::TextureFormat,
antialiasing: Option<settings::Antialiasing>,
) -> Pipeline {
- let constant_layout =
+ let constants_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
bindings: &[wgpu::BindGroupLayoutEntry {
@@ -88,7 +93,7 @@ impl Pipeline {
let constant_bind_group =
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
- layout: &constant_layout,
+ layout: &constants_layout,
bindings: &[wgpu::Binding {
binding: 0,
resource: wgpu::BindingResource::Buffer {
@@ -100,7 +105,7 @@ impl Pipeline {
let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- bind_group_layouts: &[&constant_layout],
+ bind_group_layouts: &[&constants_layout],
});
let vs = include_bytes!("shader/triangle.vert.spv");
@@ -180,6 +185,7 @@ impl Pipeline {
Pipeline {
pipeline,
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
+ constants_layout,
constants: constant_bind_group,
uniforms_buffer: constants_buffer,
vertex_buffer: Buffer::new(
@@ -220,9 +226,25 @@ impl Pipeline {
// Then we ensure the current buffers are big enough, resizing if
// necessary
- self.uniforms_buffer.ensure_capacity(device, meshes.len());
- self.vertex_buffer.ensure_capacity(device, total_vertices);
- self.index_buffer.ensure_capacity(device, total_indices);
+ let _ = self.vertex_buffer.expand(device, total_vertices);
+ let _ = self.index_buffer.expand(device, total_indices);
+
+ // If the uniforms buffer is resized, then we need to recreate its
+ // bind group.
+ if self.uniforms_buffer.expand(device, meshes.len()) {
+ self.constants =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: None,
+ layout: &self.constants_layout,
+ bindings: &[wgpu::Binding {
+ binding: 0,
+ resource: wgpu::BindingResource::Buffer {
+ buffer: &self.uniforms_buffer.raw,
+ range: 0..std::mem::size_of::<Uniforms>() as u64,
+ },
+ }],
+ });
+ }
let mut uniforms: Vec<Uniforms> = Vec::with_capacity(meshes.len());
let mut offsets: Vec<(
diff --git a/wgpu/src/widget/slider.rs b/wgpu/src/widget/slider.rs
index cf036829..3a8c2595 100644
--- a/wgpu/src/widget/slider.rs
+++ b/wgpu/src/widget/slider.rs
@@ -13,4 +13,4 @@ pub use iced_native::slider::State;
/// values.
///
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
-pub type Slider<'a, Message> = iced_native::Slider<'a, Message, Renderer>;
+pub type Slider<'a, T, Message> = iced_native::Slider<'a, T, Message, Renderer>;
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index 8345679a..5bdd34bc 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -1,4 +1,4 @@
-use crate::{Backend, Renderer, Settings};
+use crate::{Backend, Color, Renderer, Settings};
use iced_graphics::Viewport;
use iced_native::{futures, mouse};
@@ -103,6 +103,7 @@ impl iced_graphics::window::Compositor for Compositor {
renderer: &mut Self::Renderer,
swap_chain: &mut Self::SwapChain,
viewport: &Viewport,
+ background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T],
) -> mouse::Interaction {
@@ -118,11 +119,15 @@ impl iced_graphics::window::Compositor for Compositor {
resolve_target: None,
load_op: wgpu::LoadOp::Clear,
store_op: wgpu::StoreOp::Store,
- clear_color: wgpu::Color {
- r: 1.0,
- g: 1.0,
- b: 1.0,
- a: 1.0,
+ clear_color: {
+ let [r, g, b, a] = background_color.into_linear();
+
+ wgpu::Color {
+ r: f64::from(r),
+ g: f64::from(g),
+ b: f64::from(b),
+ a: f64::from(a),
+ }
},
}],
depth_stencil_attachment: None,
diff --git a/winit/src/application.rs b/winit/src/application.rs
index 73ac72b2..5b93c8af 100644
--- a/winit/src/application.rs
+++ b/winit/src/application.rs
@@ -1,6 +1,6 @@
//! Create interactive, native cross-platform applications.
use crate::{
- conversion, mouse, Clipboard, Command, Debug, Executor, Mode, Proxy,
+ conversion, mouse, Clipboard, Color, Command, Debug, Executor, Mode, Proxy,
Runtime, Settings, Size, Subscription,
};
use iced_graphics::window;
@@ -73,6 +73,32 @@ pub trait Application: Program {
fn mode(&self) -> Mode {
Mode::Windowed
}
+
+ /// Returns the background [`Color`] of the [`Application`].
+ ///
+ /// By default, it returns [`Color::WHITE`].
+ ///
+ /// [`Color`]: struct.Color.html
+ /// [`Application`]: trait.Application.html
+ /// [`Color::WHITE`]: struct.Color.html#const.WHITE
+ fn background_color(&self) -> Color {
+ Color::WHITE
+ }
+
+ /// Returns the scale factor of the [`Application`].
+ ///
+ /// It can be used to dynamically control the size of the UI at runtime
+ /// (i.e. zooming).
+ ///
+ /// For instance, a scale factor of `2.0` will make widgets twice as big,
+ /// while a scale factor of `0.5` will shrink them to half their size.
+ ///
+ /// By default, it returns `1.0`.
+ ///
+ /// [`Application`]: trait.Application.html
+ fn scale_factor(&self) -> f64 {
+ 1.0
+ }
}
/// Runs an [`Application`] with an executor, compositor, and the provided
@@ -112,6 +138,8 @@ pub fn run<A, E, C>(
let mut title = application.title();
let mut mode = application.mode();
+ let mut background_color = application.background_color();
+ let mut scale_factor = application.scale_factor();
let window = settings
.window
@@ -120,13 +148,14 @@ pub fn run<A, E, C>(
.expect("Open window");
let clipboard = Clipboard::new(&window);
+ let mut cursor_position = winit::dpi::PhysicalPosition::new(-1.0, -1.0);
let mut mouse_interaction = mouse::Interaction::default();
let mut modifiers = winit::event::ModifiersState::default();
let physical_size = window.inner_size();
let mut viewport = Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height),
- window.scale_factor(),
+ window.scale_factor() * scale_factor,
);
let mut resized = false;
@@ -143,6 +172,7 @@ pub fn run<A, E, C>(
let mut state = program::State::new(
application,
viewport.logical_size(),
+ conversion::cursor_position(cursor_position, viewport.scale_factor()),
&mut renderer,
&mut debug,
);
@@ -150,10 +180,18 @@ pub fn run<A, E, C>(
event_loop.run(move |event, _, control_flow| match event {
event::Event::MainEventsCleared => {
+ if state.is_queue_empty() {
+ return;
+ }
+
let command = runtime.enter(|| {
state.update(
- clipboard.as_ref().map(|c| c as _),
viewport.logical_size(),
+ conversion::cursor_position(
+ cursor_position,
+ viewport.scale_factor(),
+ ),
+ clipboard.as_ref().map(|c| c as _),
&mut renderer,
&mut debug,
)
@@ -189,6 +227,39 @@ pub fn run<A, E, C>(
mode = new_mode;
}
+
+ // Update background color
+ background_color = program.background_color();
+
+ // Update scale factor
+ let new_scale_factor = program.scale_factor();
+
+ if scale_factor != new_scale_factor {
+ let size = window.inner_size();
+
+ viewport = Viewport::with_physical_size(
+ Size::new(size.width, size.height),
+ window.scale_factor() * new_scale_factor,
+ );
+
+ // We relayout the UI with the new logical size.
+ // The queue is empty, therefore this will never produce
+ // a `Command`.
+ //
+ // TODO: Properly queue `WindowResized`
+ let _ = state.update(
+ viewport.logical_size(),
+ conversion::cursor_position(
+ cursor_position,
+ viewport.scale_factor(),
+ ),
+ clipboard.as_ref().map(|c| c as _),
+ &mut renderer,
+ &mut debug,
+ );
+
+ scale_factor = new_scale_factor;
+ }
}
window.request_redraw();
@@ -215,6 +286,7 @@ pub fn run<A, E, C>(
&mut renderer,
&mut swap_chain,
&viewport,
+ background_color,
state.primitive(),
&debug.overlay(),
);
@@ -239,7 +311,9 @@ pub fn run<A, E, C>(
handle_window_event(
&window_event,
&window,
+ scale_factor,
control_flow,
+ &mut cursor_position,
&mut modifiers,
&mut viewport,
&mut resized,
@@ -266,7 +340,9 @@ pub fn run<A, E, C>(
pub fn handle_window_event(
event: &winit::event::WindowEvent<'_>,
window: &winit::window::Window,
+ scale_factor: f64,
control_flow: &mut winit::event_loop::ControlFlow,
+ cursor_position: &mut winit::dpi::PhysicalPosition<f64>,
modifiers: &mut winit::event::ModifiersState,
viewport: &mut Viewport,
resized: &mut bool,
@@ -278,13 +354,18 @@ pub fn handle_window_event(
WindowEvent::Resized(new_size) => {
let size = Size::new(new_size.width, new_size.height);
- *viewport =
- Viewport::with_physical_size(size, window.scale_factor());
+ *viewport = Viewport::with_physical_size(
+ size,
+ window.scale_factor() * scale_factor,
+ );
*resized = true;
}
WindowEvent::CloseRequested => {
*control_flow = ControlFlow::Exit;
}
+ WindowEvent::CursorMoved { position, .. } => {
+ *cursor_position = *position;
+ }
WindowEvent::ModifiersChanged(new_modifiers) => {
*modifiers = *new_modifiers;
}
diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs
index b887db6e..cfa76e88 100644
--- a/winit/src/conversion.rs
+++ b/winit/src/conversion.rs
@@ -4,7 +4,7 @@
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
use crate::{
keyboard::{self, KeyCode, ModifiersState},
- mouse, window, Event, Mode,
+ mouse, window, Event, Mode, Point,
};
/// Converts a winit window event into an iced event.
@@ -92,6 +92,9 @@ pub fn window_event(
}
}
})),
+ WindowEvent::ModifiersChanged(new_modifiers) => Some(Event::Keyboard(
+ keyboard::Event::ModifiersChanged(modifiers_state(*new_modifiers)),
+ )),
WindowEvent::HoveredFile(path) => {
Some(Event::Window(window::Event::FileHovered(path.clone())))
}
@@ -174,6 +177,16 @@ pub fn modifiers_state(
}
}
+/// Converts a physical cursor position to a logical `Point`.
+pub fn cursor_position(
+ position: winit::dpi::PhysicalPosition<f64>,
+ scale_factor: f64,
+) -> Point {
+ let logical_position = position.to_logical(scale_factor);
+
+ Point::new(logical_position.x, logical_position.y)
+}
+
/// Converts a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code.
///
/// [`winit`]: https://github.com/rust-windowing/winit
diff --git a/winit/src/settings.rs b/winit/src/settings.rs
index 37cb832f..4155bf7d 100644
--- a/winit/src/settings.rs
+++ b/winit/src/settings.rs
@@ -14,7 +14,7 @@ use winit::monitor::MonitorHandle;
use winit::window::WindowBuilder;
/// The settings of an application.
-#[derive(Debug, Clone, Copy, PartialEq, Default)]
+#[derive(Debug, Clone, Default)]
pub struct Settings<Flags> {
/// The [`Window`] settings
///
@@ -28,17 +28,26 @@ pub struct Settings<Flags> {
}
/// The window settings of an application.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Clone)]
pub struct Window {
/// The size of the window.
pub size: (u32, u32),
+ /// The minimum size of the window.
+ pub min_size: Option<(u32, u32)>,
+
+ /// The maximum size of the window.
+ pub max_size: Option<(u32, u32)>,
+
/// Whether the window should be resizable or not.
pub resizable: bool,
/// Whether the window should have a border, a title bar, etc.
pub decorations: bool,
+ /// The window icon, which is also usually used in the taskbar
+ pub icon: Option<winit::window::Icon>,
+
/// Platform specific settings.
pub platform_specific: platform::PlatformSpecific,
}
@@ -60,8 +69,19 @@ impl Window {
.with_inner_size(winit::dpi::LogicalSize { width, height })
.with_resizable(self.resizable)
.with_decorations(self.decorations)
+ .with_window_icon(self.icon)
.with_fullscreen(conversion::fullscreen(primary_monitor, mode));
+ if let Some((width, height)) = self.min_size {
+ window_builder = window_builder
+ .with_min_inner_size(winit::dpi::LogicalSize { width, height });
+ }
+
+ if let Some((width, height)) = self.max_size {
+ window_builder = window_builder
+ .with_max_inner_size(winit::dpi::LogicalSize { width, height });
+ }
+
#[cfg(target_os = "windows")]
{
use winit::platform::windows::WindowBuilderExtWindows;
@@ -79,8 +99,11 @@ impl Default for Window {
fn default() -> Window {
Window {
size: (1024, 768),
+ min_size: None,
+ max_size: None,
resizable: true,
decorations: true,
+ icon: None,
platform_specific: Default::default(),
}
}