Merge branch 'master' into feature/pane-grid-titlebar

This commit is contained in:
Héctor Ramón Jiménez 2020-07-08 11:44:40 +02:00
commit f3dfaa2c43
59 changed files with 1064 additions and 311 deletions

View File

@ -28,4 +28,7 @@ pub enum Event {
/// A unicode character was received. /// A unicode character was received.
CharacterReceived(char), CharacterReceived(char),
/// The keyboard modifiers have changed.
ModifiersChanged(ModifiersState),
} }

View File

@ -269,7 +269,7 @@ struct ColorPicker<C: ColorSpace> {
trait ColorSpace: Sized { trait ColorSpace: Sized {
const LABEL: &'static str; 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; 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 [s1, s2, s3] = &mut self.sliders;
let [cr1, cr2, cr3] = C::COMPONENT_RANGES; 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() Row::new()
.spacing(10) .spacing(10)
.align_items(Align::Center) .align_items(Align::Center)
.push(Text::new(C::LABEL).width(Length::Units(50))) .push(Text::new(C::LABEL).width(Length::Units(50)))
.push(Slider::new(s1, cr1, c1, move |v| C::new(v, c2, c3))) .push(slider(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(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(s3, cr3, c3, move |v| C::new(c1, c2, v)))
.push( .push(
Text::new(color.to_string()) Text::new(color.to_string())
.width(Length::Units(185)) .width(Length::Units(185))
@ -302,7 +314,7 @@ impl<C: 'static + ColorSpace + Copy> ColorPicker<C> {
impl ColorSpace for Color { impl ColorSpace for Color {
const LABEL: &'static str = "RGB"; 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]; [0.0..=1.0, 0.0..=1.0, 0.0..=1.0];
fn new(r: f32, g: f32, b: f32) -> Self { fn new(r: f32, g: f32, b: f32) -> Self {
@ -325,7 +337,7 @@ impl ColorSpace for Color {
impl ColorSpace for palette::Hsl { impl ColorSpace for palette::Hsl {
const LABEL: &'static str = "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]; [0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
fn new(hue: f32, saturation: f32, lightness: f32) -> Self { fn new(hue: f32, saturation: f32, lightness: f32) -> Self {
@ -356,7 +368,7 @@ impl ColorSpace for palette::Hsl {
impl ColorSpace for palette::Hsv { impl ColorSpace for palette::Hsv {
const LABEL: &'static str = "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]; [0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
fn new(hue: f32, saturation: f32, value: f32) -> Self { fn new(hue: f32, saturation: f32, value: f32) -> Self {
@ -379,7 +391,7 @@ impl ColorSpace for palette::Hsv {
impl ColorSpace for palette::Hwb { impl ColorSpace for palette::Hwb {
const LABEL: &'static str = "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]; [0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
fn new(hue: f32, whiteness: f32, blackness: f32) -> Self { fn new(hue: f32, whiteness: f32, blackness: f32) -> Self {
@ -410,7 +422,7 @@ impl ColorSpace for palette::Hwb {
impl ColorSpace for palette::Lab { impl ColorSpace for palette::Lab {
const LABEL: &'static str = "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]; [0.0..=100.0, -128.0..=127.0, -128.0..=127.0];
fn new(l: f32, a: f32, b: f32) -> Self { fn new(l: f32, a: f32, b: f32) -> Self {
@ -428,7 +440,7 @@ impl ColorSpace for palette::Lab {
impl ColorSpace for palette::Lch { impl ColorSpace for palette::Lch {
const LABEL: &'static str = "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]; [0.0..=100.0, 0.0..=128.0, 0.0..=360.0];
fn new(l: f32, chroma: f32, hue: f32) -> Self { fn new(l: f32, chroma: f32, hue: f32) -> Self {

View File

@ -48,24 +48,33 @@ impl Program for Controls {
let sliders = Row::new() let sliders = Row::new()
.width(Length::Units(500)) .width(Length::Units(500))
.spacing(20) .spacing(20)
.push(Slider::new(r, 0.0..=1.0, background_color.r, move |r| { .push(
Message::BackgroundColorChanged(Color { Slider::new(r, 0.0..=1.0, background_color.r, move |r| {
r, Message::BackgroundColorChanged(Color {
..background_color r,
..background_color
})
}) })
})) .step(0.01),
.push(Slider::new(g, 0.0..=1.0, background_color.g, move |g| { )
Message::BackgroundColorChanged(Color { .push(
g, Slider::new(g, 0.0..=1.0, background_color.g, move |g| {
..background_color Message::BackgroundColorChanged(Color {
g,
..background_color
})
}) })
})) .step(0.01),
.push(Slider::new(b, 0.0..=1.0, background_color.b, move |b| { )
Message::BackgroundColorChanged(Color { .push(
b, Slider::new(b, 0.0..=1.0, background_color.b, move |b| {
..background_color Message::BackgroundColorChanged(Color {
b,
..background_color
})
}) })
})); .step(0.01),
);
Row::new() Row::new()
.width(Length::Fill) .width(Length::Fill)

View File

@ -5,9 +5,10 @@ use controls::Controls;
use scene::Scene; use scene::Scene;
use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; 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::{ use winit::{
dpi::PhysicalPosition,
event::{Event, ModifiersState, WindowEvent}, event::{Event, ModifiersState, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
}; };
@ -24,6 +25,7 @@ pub fn main() {
Size::new(physical_size.width, physical_size.height), Size::new(physical_size.width, physical_size.height),
window.scale_factor(), window.scale_factor(),
); );
let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
let mut modifiers = ModifiersState::default(); let mut modifiers = ModifiersState::default();
// Initialize wgpu // Initialize wgpu
@ -79,6 +81,7 @@ pub fn main() {
let mut state = program::State::new( let mut state = program::State::new(
controls, controls,
viewport.logical_size(), viewport.logical_size(),
conversion::cursor_position(cursor_position, viewport.scale_factor()),
&mut renderer, &mut renderer,
&mut debug, &mut debug,
); );
@ -91,6 +94,9 @@ pub fn main() {
match event { match event {
Event::WindowEvent { event, .. } => { Event::WindowEvent { event, .. } => {
match event { match event {
WindowEvent::CursorMoved { position, .. } => {
cursor_position = position;
}
WindowEvent::ModifiersChanged(new_modifiers) => { WindowEvent::ModifiersChanged(new_modifiers) => {
modifiers = new_modifiers; modifiers = new_modifiers;
} }
@ -105,7 +111,6 @@ pub fn main() {
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
} }
_ => {} _ => {}
} }
@ -119,16 +124,23 @@ pub fn main() {
} }
} }
Event::MainEventsCleared => { Event::MainEventsCleared => {
// We update iced // If there are events pending
let _ = state.update( if !state.is_queue_empty() {
None, // We update iced
viewport.logical_size(), let _ = state.update(
&mut renderer, viewport.logical_size(),
&mut debug, conversion::cursor_position(
); cursor_position,
viewport.scale_factor(),
),
None,
&mut renderer,
&mut debug,
);
// and request a redraw // and request a redraw
window.request_redraw(); window.request_redraw();
}
} }
Event::RedrawRequested(_) => { Event::RedrawRequested(_) => {
if resized { if resized {

View File

@ -36,12 +36,15 @@ impl Sandbox for Progress {
Column::new() Column::new()
.padding(20) .padding(20)
.push(ProgressBar::new(0.0..=100.0, self.value)) .push(ProgressBar::new(0.0..=100.0, self.value))
.push(Slider::new( .push(
&mut self.progress_bar_slider, Slider::new(
0.0..=100.0, &mut self.progress_bar_slider,
self.value, 0.0..=100.0,
Message::SliderChanged, self.value,
)) Message::SliderChanged,
)
.step(0.01),
)
.into() .into()
} }
} }

View File

@ -190,7 +190,7 @@ enum Step {
Welcome, Welcome,
Slider { Slider {
state: slider::State, state: slider::State,
value: u16, value: u8,
}, },
RowsAndColumns { RowsAndColumns {
layout: Layout, layout: Layout,
@ -222,13 +222,13 @@ enum Step {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum StepMessage { pub enum StepMessage {
SliderChanged(f32), SliderChanged(u8),
LayoutChanged(Layout), LayoutChanged(Layout),
SpacingChanged(f32), SpacingChanged(u16),
TextSizeChanged(f32), TextSizeChanged(u16),
TextColorChanged(Color), TextColorChanged(Color),
LanguageSelected(Language), LanguageSelected(Language),
ImageWidthChanged(f32), ImageWidthChanged(u16),
InputChanged(String), InputChanged(String),
ToggleSecureInput(bool), ToggleSecureInput(bool),
DebugToggled(bool), DebugToggled(bool),
@ -249,12 +249,12 @@ impl<'a> Step {
} }
StepMessage::SliderChanged(new_value) => { StepMessage::SliderChanged(new_value) => {
if let Step::Slider { value, .. } = self { if let Step::Slider { value, .. } = self {
*value = new_value.round() as u16; *value = new_value;
} }
} }
StepMessage::TextSizeChanged(new_size) => { StepMessage::TextSizeChanged(new_size) => {
if let Step::Text { size, .. } = self { if let Step::Text { size, .. } = self {
*size = new_size.round() as u16; *size = new_size;
} }
} }
StepMessage::TextColorChanged(new_color) => { StepMessage::TextColorChanged(new_color) => {
@ -269,12 +269,12 @@ impl<'a> Step {
} }
StepMessage::SpacingChanged(new_spacing) => { StepMessage::SpacingChanged(new_spacing) => {
if let Step::RowsAndColumns { spacing, .. } = self { if let Step::RowsAndColumns { spacing, .. } = self {
*spacing = new_spacing.round() as u16; *spacing = new_spacing;
} }
} }
StepMessage::ImageWidthChanged(new_width) => { StepMessage::ImageWidthChanged(new_width) => {
if let Step::Image { width, .. } = self { if let Step::Image { width, .. } = self {
*width = new_width.round() as u16; *width = new_width;
} }
} }
StepMessage::InputChanged(new_value) => { StepMessage::InputChanged(new_value) => {
@ -384,7 +384,7 @@ impl<'a> Step {
fn slider( fn slider(
state: &'a mut slider::State, state: &'a mut slider::State,
value: u16, value: u8,
) -> Column<'a, StepMessage> { ) -> Column<'a, StepMessage> {
Self::container("Slider") Self::container("Slider")
.push(Text::new( .push(Text::new(
@ -397,8 +397,8 @@ impl<'a> Step {
)) ))
.push(Slider::new( .push(Slider::new(
state, state,
0.0..=100.0, 0..=100,
value as f32, value,
StepMessage::SliderChanged, StepMessage::SliderChanged,
)) ))
.push( .push(
@ -444,8 +444,8 @@ impl<'a> Step {
.spacing(10) .spacing(10)
.push(Slider::new( .push(Slider::new(
spacing_slider, spacing_slider,
0.0..=80.0, 0..=80,
spacing as f32, spacing,
StepMessage::SpacingChanged, StepMessage::SpacingChanged,
)) ))
.push( .push(
@ -486,30 +486,25 @@ impl<'a> Step {
) )
.push(Slider::new( .push(Slider::new(
size_slider, size_slider,
10.0..=70.0, 10..=70,
size as f32, size,
StepMessage::TextSizeChanged, StepMessage::TextSizeChanged,
)); ));
let [red, green, blue] = color_sliders; 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() let color_section = Column::new()
.padding(20) .padding(20)
.spacing(20) .spacing(20)
.push(Text::new("And its color:")) .push(Text::new("And its color:"))
.push(Text::new(&format!("{:?}", color)).color(color)) .push(Text::new(&format!("{:?}", color)).color(color))
.push( .push(color_sliders);
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 })
})),
);
Self::container("Text") Self::container("Text")
.push(Text::new( .push(Text::new(
@ -559,8 +554,8 @@ impl<'a> Step {
.push(ferris(width)) .push(ferris(width))
.push(Slider::new( .push(Slider::new(
slider, slider,
100.0..=500.0, 100..=500,
width as f32, width,
StepMessage::ImageWidthChanged, StepMessage::ImageWidthChanged,
)) ))
.push( .push(
@ -706,6 +701,17 @@ fn button<'a, Message>(
.min_width(100) .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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language { pub enum Language {
Rust, Rust,

View File

@ -18,6 +18,7 @@ pub struct Backend {
quad_pipeline: quad::Pipeline, quad_pipeline: quad::Pipeline,
text_pipeline: text::Pipeline, text_pipeline: text::Pipeline,
triangle_pipeline: triangle::Pipeline, triangle_pipeline: triangle::Pipeline,
default_text_size: u16,
} }
impl Backend { impl Backend {
@ -33,6 +34,7 @@ impl Backend {
quad_pipeline, quad_pipeline,
text_pipeline, text_pipeline,
triangle_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 ICON_FONT: Font = font::ICONS;
const CHECKMARK_ICON: char = font::CHECKMARK_ICON; const CHECKMARK_ICON: char = font::CHECKMARK_ICON;
fn default_size(&self) -> u16 {
self.default_text_size
}
fn measure( fn measure(
&self, &self,
contents: &str, contents: &str,

View File

@ -11,6 +11,11 @@ pub struct Settings {
/// If `None` is provided, a default system font will be chosen. /// If `None` is provided, a default system font will be chosen.
pub default_font: Option<&'static [u8]>, 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. /// The antialiasing strategy that will be used for triangle primitives.
pub antialiasing: Option<Antialiasing>, pub antialiasing: Option<Antialiasing>,
} }
@ -19,6 +24,7 @@ impl Default for Settings {
fn default() -> Settings { fn default() -> Settings {
Settings { Settings {
default_font: None, default_font: None,
default_text_size: 20,
antialiasing: None, antialiasing: None,
} }
} }

View File

@ -13,4 +13,4 @@ pub use iced_native::slider::State;
/// values. /// values.
/// ///
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. /// 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>;

View File

@ -1,4 +1,4 @@
use crate::{Backend, Renderer, Settings, Viewport}; use crate::{Backend, Color, Renderer, Settings, Viewport};
use core::ffi::c_void; use core::ffi::c_void;
use glow::HasContext; use glow::HasContext;
@ -21,8 +21,6 @@ impl iced_graphics::window::GLCompositor for Compositor {
) -> (Self, Self::Renderer) { ) -> (Self, Self::Renderer) {
let gl = glow::Context::from_loader_function(loader_function); 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 // Enable auto-conversion from/to sRGB
gl.enable(glow::FRAMEBUFFER_SRGB); gl.enable(glow::FRAMEBUFFER_SRGB);
@ -60,12 +58,16 @@ impl iced_graphics::window::GLCompositor for Compositor {
&mut self, &mut self,
renderer: &mut Self::Renderer, renderer: &mut Self::Renderer,
viewport: &Viewport, viewport: &Viewport,
color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output, output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T], overlay: &[T],
) -> mouse::Interaction { ) -> mouse::Interaction {
let gl = &self.gl; let gl = &self.gl;
let [r, g, b, a] = color.into_linear();
unsafe { unsafe {
gl.clear_color(r, g, b, a);
gl.clear(glow::COLOR_BUFFER_BIT); gl.clear(glow::COLOR_BUFFER_BIT);
} }

View File

@ -47,6 +47,8 @@ pub fn run<A, E, C>(
let mut title = application.title(); let mut title = application.title();
let mut mode = application.mode(); let mut mode = application.mode();
let mut background_color = application.background_color();
let mut scale_factor = application.scale_factor();
let context = { let context = {
let builder = settings.window.into_builder( let builder = settings.window.into_builder(
@ -68,13 +70,14 @@ pub fn run<A, E, C>(
}; };
let clipboard = Clipboard::new(&context.window()); 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 mouse_interaction = mouse::Interaction::default();
let mut modifiers = glutin::event::ModifiersState::default(); let mut modifiers = glutin::event::ModifiersState::default();
let physical_size = context.window().inner_size(); let physical_size = context.window().inner_size();
let mut viewport = Viewport::with_physical_size( let mut viewport = Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height), Size::new(physical_size.width, physical_size.height),
context.window().scale_factor(), context.window().scale_factor() * scale_factor,
); );
let mut resized = false; let mut resized = false;
@ -88,6 +91,7 @@ pub fn run<A, E, C>(
let mut state = program::State::new( let mut state = program::State::new(
application, application,
viewport.logical_size(), viewport.logical_size(),
conversion::cursor_position(cursor_position, viewport.scale_factor()),
&mut renderer, &mut renderer,
&mut debug, &mut debug,
); );
@ -95,10 +99,18 @@ pub fn run<A, E, C>(
event_loop.run(move |event, _, control_flow| match event { event_loop.run(move |event, _, control_flow| match event {
event::Event::MainEventsCleared => { event::Event::MainEventsCleared => {
if state.is_queue_empty() {
return;
}
let command = runtime.enter(|| { let command = runtime.enter(|| {
state.update( state.update(
clipboard.as_ref().map(|c| c as _),
viewport.logical_size(), viewport.logical_size(),
conversion::cursor_position(
cursor_position,
viewport.scale_factor(),
),
clipboard.as_ref().map(|c| c as _),
&mut renderer, &mut renderer,
&mut debug, &mut debug,
) )
@ -134,6 +146,39 @@ pub fn run<A, E, C>(
mode = new_mode; 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(); context.window().request_redraw();
@ -160,6 +205,7 @@ pub fn run<A, E, C>(
let new_mouse_interaction = compositor.draw( let new_mouse_interaction = compositor.draw(
&mut renderer, &mut renderer,
&viewport, &viewport,
background_color,
state.primitive(), state.primitive(),
&debug.overlay(), &debug.overlay(),
); );
@ -186,7 +232,9 @@ pub fn run<A, E, C>(
application::handle_window_event( application::handle_window_event(
&window_event, &window_event,
context.window(), context.window(),
scale_factor,
control_flow, control_flow,
&mut cursor_position,
&mut modifiers, &mut modifiers,
&mut viewport, &mut viewport,
&mut resized, &mut resized,

View File

@ -25,6 +25,9 @@ pub trait Text {
/// [`ICON_FONT`]: #associatedconst.ICON_FONt /// [`ICON_FONT`]: #associatedconst.ICON_FONt
const CHECKMARK_ICON: char; 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, /// Measures the text contents with the given size and font,
/// returning the size of a laid out paragraph that fits in the provided /// returning the size of a laid out paragraph that fits in the provided
/// bounds. /// bounds.

View File

@ -9,7 +9,6 @@
#![forbid(rust_2018_idioms)] #![forbid(rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
mod antialiasing; mod antialiasing;
mod defaults;
mod primitive; mod primitive;
mod renderer; mod renderer;
mod transformation; mod transformation;
@ -17,6 +16,7 @@ mod viewport;
mod widget; mod widget;
pub mod backend; pub mod backend;
pub mod defaults;
pub mod font; pub mod font;
pub mod layer; pub mod layer;
pub mod triangle; pub mod triangle;
@ -35,6 +35,6 @@ pub use transformation::Transformation;
pub use viewport::Viewport; pub use viewport::Viewport;
pub use iced_native::{ pub use iced_native::{
Background, Font, HorizontalAlignment, Point, Rectangle, Size, Vector, Background, Color, Font, HorizontalAlignment, Point, Rectangle, Size,
VerticalAlignment, Vector, VerticalAlignment,
}; };

View File

@ -103,7 +103,7 @@ where
} else { } else {
content content
}, },
if is_mouse_over { if is_mouse_over && !is_disabled {
mouse::Interaction::Pointer mouse::Interaction::Pointer
} else { } else {
mouse::Interaction::default() mouse::Interaction::default()

View File

@ -84,7 +84,7 @@ impl Builder {
radii: math::Vector::new(arc.radii.x, arc.radii.y), radii: math::Vector::new(arc.radii.x, arc.radii.y),
x_rotation: math::Angle::radians(arc.rotation), x_rotation: math::Angle::radians(arc.rotation),
start_angle: math::Angle::radians(arc.start_angle), 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)); let _ = self.raw.move_to(arc.sample(0.0));

View File

@ -16,8 +16,8 @@ pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet};
/// values. /// values.
/// ///
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. /// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`.
pub type Slider<'a, Message, Backend> = pub type Slider<'a, T, Message, Backend> =
iced_native::Slider<'a, Message, Renderer<Backend>>; iced_native::Slider<'a, T, Message, Renderer<Backend>>;
const HANDLE_HEIGHT: f32 = 22.0; const HANDLE_HEIGHT: f32 = 22.0;

View File

@ -20,7 +20,9 @@ where
{ {
type Font = Font; type Font = Font;
const DEFAULT_SIZE: u16 = 20; fn default_size(&self) -> u16 {
self.backend().default_size()
}
fn measure( fn measure(
&self, &self,

View File

@ -27,14 +27,8 @@ impl<B> text_input::Renderer for Renderer<B>
where where
B: Backend + backend::Text, B: Backend + backend::Text,
{ {
type Font = Font;
type Style = Box<dyn StyleSheet>; 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 { fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 {
let backend = self.backend(); let backend = self.backend();

View File

@ -1,4 +1,4 @@
use crate::Viewport; use crate::{Color, Viewport};
use iced_native::mouse; use iced_native::mouse;
use raw_window_handle::HasRawWindowHandle; use raw_window_handle::HasRawWindowHandle;
@ -49,6 +49,7 @@ pub trait Compositor: Sized {
renderer: &mut Self::Renderer, renderer: &mut Self::Renderer,
swap_chain: &mut Self::SwapChain, swap_chain: &mut Self::SwapChain,
viewport: &Viewport, viewport: &Viewport,
background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output, output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T], overlay: &[T],
) -> mouse::Interaction; ) -> mouse::Interaction;

View File

@ -1,4 +1,4 @@
use crate::{Size, Viewport}; use crate::{Color, Size, Viewport};
use iced_native::mouse; use iced_native::mouse;
use core::ffi::c_void; use core::ffi::c_void;
@ -61,6 +61,7 @@ pub trait GLCompositor: Sized {
&mut self, &mut self,
renderer: &mut Self::Renderer, renderer: &mut Self::Renderer,
viewport: &Viewport, viewport: &Viewport,
background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output, output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T], overlay: &[T],
) -> mouse::Interaction; ) -> mouse::Interaction;

View File

@ -13,6 +13,7 @@ debug = []
[dependencies] [dependencies]
twox-hash = "1.5" twox-hash = "1.5"
unicode-segmentation = "1.6" unicode-segmentation = "1.6"
num-traits = "0.2"
[dependencies.iced_core] [dependencies.iced_core]
version = "0.2" version = "0.2"

View File

@ -174,9 +174,13 @@ impl Debug {
lines.push(key_value("Render:", self.render_durations.average())); lines.push(key_value("Render:", self.render_durations.average()));
lines.push(key_value("Message count:", self.message_count)); lines.push(key_value("Message count:", self.message_count));
lines.push(String::from("Last messages:")); lines.push(String::from("Last messages:"));
lines.extend( lines.extend(self.last_messages.iter().map(|msg| {
self.last_messages.iter().map(|msg| format!(" {}", msg)), if msg.len() <= 100 {
); format!(" {}", msg)
} else {
format!(" {:.100}...", msg)
}
}));
lines lines
} }

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
Cache, Clipboard, Command, Debug, Event, Program, Renderer, Size, Cache, Clipboard, Command, Debug, Event, Point, Program, Renderer, Size,
UserInterface, UserInterface,
}; };
@ -31,6 +31,7 @@ where
pub fn new( pub fn new(
mut program: P, mut program: P,
bounds: Size, bounds: Size,
cursor_position: Point,
renderer: &mut P::Renderer, renderer: &mut P::Renderer,
debug: &mut Debug, debug: &mut Debug,
) -> Self { ) -> Self {
@ -43,7 +44,7 @@ where
); );
debug.draw_started(); debug.draw_started();
let primitive = user_interface.draw(renderer); let primitive = user_interface.draw(renderer, cursor_position);
debug.draw_finished(); debug.draw_finished();
let cache = Some(user_interface.into_cache()); let cache = Some(user_interface.into_cache());
@ -88,6 +89,13 @@ where
self.queued_messages.push(message); 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 /// Processes all the queued events and messages, rebuilding and redrawing
/// the widgets of the linked [`Program`] if necessary. /// the widgets of the linked [`Program`] if necessary.
/// ///
@ -97,15 +105,12 @@ where
/// [`Program`]: trait.Program.html /// [`Program`]: trait.Program.html
pub fn update( pub fn update(
&mut self, &mut self,
clipboard: Option<&dyn Clipboard>,
bounds: Size, bounds: Size,
cursor_position: Point,
clipboard: Option<&dyn Clipboard>,
renderer: &mut P::Renderer, renderer: &mut P::Renderer,
debug: &mut Debug, debug: &mut Debug,
) -> Option<Command<P::Message>> { ) -> Option<Command<P::Message>> {
if self.queued_events.is_empty() && self.queued_messages.is_empty() {
return None;
}
let mut user_interface = build_user_interface( let mut user_interface = build_user_interface(
&mut self.program, &mut self.program,
self.cache.take().unwrap(), self.cache.take().unwrap(),
@ -117,6 +122,7 @@ where
debug.event_processing_started(); debug.event_processing_started();
let mut messages = user_interface.update( let mut messages = user_interface.update(
self.queued_events.drain(..), self.queued_events.drain(..),
cursor_position,
clipboard, clipboard,
renderer, renderer,
); );
@ -125,7 +131,7 @@ where
if messages.is_empty() { if messages.is_empty() {
debug.draw_started(); debug.draw_started();
self.primitive = user_interface.draw(renderer); self.primitive = user_interface.draw(renderer, cursor_position);
debug.draw_finished(); debug.draw_finished();
self.cache = Some(user_interface.into_cache()); self.cache = Some(user_interface.into_cache());
@ -156,7 +162,7 @@ where
); );
debug.draw_started(); debug.draw_started();
self.primitive = user_interface.draw(renderer); self.primitive = user_interface.draw(renderer, cursor_position);
debug.draw_finished(); debug.draw_finished();
self.cache = Some(user_interface.into_cache()); self.cache = Some(user_interface.into_cache());

View File

@ -50,7 +50,9 @@ impl row::Renderer for Null {
impl text::Renderer for Null { impl text::Renderer for Null {
type Font = Font; type Font = Font;
const DEFAULT_SIZE: u16 = 20; fn default_size(&self) -> u16 {
20
}
fn measure( fn measure(
&self, &self,
@ -104,13 +106,8 @@ impl scrollable::Renderer for Null {
} }
impl text_input::Renderer for Null { impl text_input::Renderer for Null {
type Font = Font;
type Style = (); type Style = ();
fn default_size(&self) -> u16 {
20
}
fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 { fn measure_value(&self, _value: &str, _size: u16, _font: Font) -> f32 {
0.0 0.0
} }

View File

@ -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; use std::hash::Hasher;
@ -23,7 +23,6 @@ pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>, root: Element<'a, Message, Renderer>,
layout: layout::Node, layout: layout::Node,
bounds: Size, bounds: Size,
cursor_position: Point,
} }
impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer> impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer>
@ -115,7 +114,6 @@ where
root, root,
layout, layout,
bounds, bounds,
cursor_position: cache.cursor_position,
} }
} }
@ -132,7 +130,7 @@ where
/// completing [the previous example](#example): /// completing [the previous example](#example):
/// ///
/// ```no_run /// ```no_run
/// use iced_native::{UserInterface, Cache, Size}; /// use iced_native::{UserInterface, Cache, Size, Point};
/// use iced_wgpu::Renderer; /// use iced_wgpu::Renderer;
/// ///
/// # mod iced_wgpu { /// # mod iced_wgpu {
@ -154,6 +152,7 @@ where
/// let mut cache = Cache::new(); /// let mut cache = Cache::new();
/// let mut renderer = Renderer::new(); /// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0); /// let mut window_size = Size::new(1024.0, 768.0);
/// let mut cursor_position = Point::default();
/// ///
/// // Initialize our event storage /// // Initialize our event storage
/// let mut events = Vec::new(); /// let mut events = Vec::new();
@ -169,7 +168,12 @@ where
/// ); /// );
/// ///
/// // Update the user interface /// // 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(); /// cache = user_interface.into_cache();
/// ///
@ -182,20 +186,17 @@ where
pub fn update( pub fn update(
&mut self, &mut self,
events: impl IntoIterator<Item = Event>, events: impl IntoIterator<Item = Event>,
cursor_position: Point,
clipboard: Option<&dyn Clipboard>, clipboard: Option<&dyn Clipboard>,
renderer: &Renderer, renderer: &Renderer,
) -> Vec<Message> { ) -> Vec<Message> {
let mut messages = Vec::new(); let mut messages = Vec::new();
for event in events { 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( self.root.widget.on_event(
event, event,
Layout::new(&self.layout), Layout::new(&self.layout),
self.cursor_position, cursor_position,
&mut messages, &mut messages,
renderer, renderer,
clipboard, clipboard,
@ -219,7 +220,7 @@ where
/// [completing the last example](#example-1): /// [completing the last example](#example-1):
/// ///
/// ```no_run /// ```no_run
/// use iced_native::{UserInterface, Cache, Size}; /// use iced_native::{UserInterface, Cache, Size, Point};
/// use iced_wgpu::Renderer; /// use iced_wgpu::Renderer;
/// ///
/// # mod iced_wgpu { /// # mod iced_wgpu {
@ -241,6 +242,7 @@ where
/// let mut cache = Cache::new(); /// let mut cache = Cache::new();
/// let mut renderer = Renderer::new(); /// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0); /// let mut window_size = Size::new(1024.0, 768.0);
/// let mut cursor_position = Point::default();
/// let mut events = Vec::new(); /// let mut events = Vec::new();
/// ///
/// loop { /// loop {
@ -253,10 +255,15 @@ where
/// &mut renderer, /// &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 /// // 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(); /// cache = user_interface.into_cache();
/// ///
@ -268,12 +275,16 @@ where
/// // Flush rendering operations... /// // 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( self.root.widget.draw(
renderer, renderer,
&Renderer::Defaults::default(), &Renderer::Defaults::default(),
Layout::new(&self.layout), Layout::new(&self.layout),
self.cursor_position, cursor_position,
) )
} }
@ -287,7 +298,6 @@ where
hash: self.hash, hash: self.hash,
layout: self.layout, layout: self.layout,
bounds: self.bounds, bounds: self.bounds,
cursor_position: self.cursor_position,
} }
} }
} }
@ -300,7 +310,6 @@ pub struct Cache {
hash: u64, hash: u64,
layout: layout::Node, layout: layout::Node,
bounds: Size, bounds: Size,
cursor_position: Point,
} }
impl Cache { impl Cache {
@ -316,7 +325,6 @@ impl Cache {
hash: 0, hash: 0,
layout: layout::Node::new(Size::new(0.0, 0.0)), layout: layout::Node::new(Size::new(0.0, 0.0)),
bounds: Size::ZERO, bounds: Size::ZERO,
cursor_position: Point::new(-1.0, -1.0),
} }
} }
} }
@ -329,7 +337,7 @@ impl Default for Cache {
impl PartialEq for Cache { impl PartialEq for Cache {
fn eq(&self, other: &Cache) -> bool { fn eq(&self, other: &Cache) -> bool {
self.hash == other.hash && self.cursor_position == other.cursor_position self.hash == other.hash
} }
} }

View File

@ -32,7 +32,7 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
width: Length, width: Length,
size: u16, size: u16,
spacing: u16, spacing: u16,
text_size: u16, text_size: Option<u16>,
style: Renderer::Style, style: Renderer::Style,
} }
@ -60,7 +60,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
width: Length::Shrink, width: Length::Shrink,
size: <Renderer as self::Renderer>::DEFAULT_SIZE, size: <Renderer as self::Renderer>::DEFAULT_SIZE,
spacing: Renderer::DEFAULT_SPACING, spacing: Renderer::DEFAULT_SPACING,
text_size: <Renderer as text::Renderer>::DEFAULT_SIZE, text_size: None,
style: Renderer::Style::default(), style: Renderer::Style::default(),
} }
} }
@ -93,7 +93,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
/// ///
/// [`Checkbox`]: struct.Checkbox.html /// [`Checkbox`]: struct.Checkbox.html
pub fn text_size(mut self, text_size: u16) -> Self { pub fn text_size(mut self, text_size: u16) -> Self {
self.text_size = text_size; self.text_size = Some(text_size);
self self
} }
@ -136,7 +136,7 @@ where
.push( .push(
Text::new(&self.label) Text::new(&self.label)
.width(self.width) .width(self.width)
.size(self.text_size), .size(self.text_size.unwrap_or(renderer.default_size())),
) )
.layout(renderer, limits) .layout(renderer, limits)
} }
@ -181,7 +181,7 @@ where
defaults, defaults,
label_layout.bounds(), label_layout.bounds(),
&self.label, &self.label,
self.text_size, self.text_size.unwrap_or(renderer.default_size()),
Default::default(), Default::default(),
None, None,
HorizontalAlignment::Left, HorizontalAlignment::Left,

View File

@ -216,6 +216,10 @@ where
/// The `leeway` describes the amount of space around a split that can be /// The `leeway` describes the amount of space around a split that can be
/// used to grab it. /// 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 /// [`PaneGrid`]: struct.PaneGrid.html
pub fn on_resize<F>(mut self, leeway: u16, f: F) -> Self pub fn on_resize<F>(mut self, leeway: u16, f: F) -> Self
where where
@ -284,8 +288,6 @@ where
self.state.focus(pane); self.state.focus(pane);
} }
} }
} else {
self.state.unfocus();
} }
} }
@ -299,7 +301,7 @@ where
if let Some((split, _)) = self.state.picked_split() { if let Some((split, _)) = self.state.picked_split() {
let bounds = layout.bounds(); let bounds = layout.bounds();
let splits = self.state.splits( let splits = self.state.split_regions(
f32::from(self.spacing), f32::from(self.spacing),
Size::new(bounds.width, bounds.height), Size::new(bounds.width, bounds.height),
); );
@ -421,7 +423,7 @@ where
let limits = limits.width(self.width).height(self.height); let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO); 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 let children = self
.elements .elements
@ -464,7 +466,7 @@ where
cursor_position.y - bounds.y, cursor_position.y - bounds.y,
); );
let splits = self.state.splits( let splits = self.state.split_regions(
f32::from(self.spacing), f32::from(self.spacing),
Size::new(bounds.width, bounds.height), Size::new(bounds.width, bounds.height),
); );
@ -493,6 +495,8 @@ where
); );
} }
} }
} else {
self.state.unfocus();
} }
} }
mouse::Event::ButtonReleased(mouse::Button::Left) => { 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; *self.pressed_modifiers = modifiers;
} }
_ => {} _ => {}
@ -601,7 +603,7 @@ where
let splits = self let splits = self
.state .state
.splits(f32::from(self.spacing), bounds.size()); .split_regions(f32::from(self.spacing), bounds.size());
hovered_split( hovered_split(
splits.iter(), splits.iter(),

View File

@ -43,12 +43,36 @@ pub enum Node {
} }
impl 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 /// Returns the rectangular region for each [`Pane`] in the [`Node`] given
/// the spacing between panes and the total available space. /// the spacing between panes and the total available space.
/// ///
/// [`Pane`]: struct.Pane.html /// [`Pane`]: struct.Pane.html
/// [`Node`]: enum.Node.html /// [`Node`]: enum.Node.html
pub fn regions( pub fn pane_regions(
&self, &self,
spacing: f32, spacing: f32,
size: Size, size: Size,
@ -75,7 +99,7 @@ impl Node {
/// ///
/// [`Split`]: struct.Split.html /// [`Split`]: struct.Split.html
/// [`Node`]: enum.Node.html /// [`Node`]: enum.Node.html
pub fn splits( pub fn split_regions(
&self, &self,
spacing: f32, spacing: f32,
size: Size, size: Size,

View File

@ -157,8 +157,10 @@ impl<T> State<T> {
/// [`Pane`]: struct.Pane.html /// [`Pane`]: struct.Pane.html
/// [`State::active`]: struct.State.html#method.active /// [`State::active`]: struct.State.html#method.active
pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> { pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> {
let regions = let regions = self
self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); .internal
.layout
.pane_regions(0.0, Size::new(4096.0, 4096.0));
let current_region = regions.get(pane)?; let current_region = regions.get(pane)?;
@ -194,6 +196,13 @@ impl<T> State<T> {
self.internal.focus(pane); 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 /// Splits the given [`Pane`] into two in the given [`Axis`] and
/// initializing the new [`Pane`] with the provided internal state. /// initializing the new [`Pane`] with the provided internal state.
/// ///
@ -366,20 +375,20 @@ impl Internal {
} }
} }
pub fn regions( pub fn pane_regions(
&self, &self,
spacing: f32, spacing: f32,
size: Size, size: Size,
) -> HashMap<Pane, Rectangle> { ) -> HashMap<Pane, Rectangle> {
self.layout.regions(spacing, size) self.layout.pane_regions(spacing, size)
} }
pub fn splits( pub fn split_regions(
&self, &self,
spacing: f32, spacing: f32,
size: Size, size: Size,
) -> HashMap<Split, (Axis, Rectangle, f32)> { ) -> HashMap<Split, (Axis, Rectangle, f32)> {
self.layout.splits(spacing, size) self.layout.split_regions(spacing, size)
} }
pub fn focus(&mut self, pane: &Pane) { pub fn focus(&mut self, pane: &Pane) {

View File

@ -41,7 +41,7 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
width: Length, width: Length,
size: u16, size: u16,
spacing: u16, spacing: u16,
text_size: u16, text_size: Option<u16>,
style: Renderer::Style, style: Renderer::Style,
} }
@ -75,7 +75,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
width: Length::Shrink, width: Length::Shrink,
size: <Renderer as self::Renderer>::DEFAULT_SIZE, size: <Renderer as self::Renderer>::DEFAULT_SIZE,
spacing: Renderer::DEFAULT_SPACING, //15 spacing: Renderer::DEFAULT_SPACING, //15
text_size: <Renderer as text::Renderer>::DEFAULT_SIZE, text_size: None,
style: Renderer::Style::default(), style: Renderer::Style::default(),
} }
} }
@ -108,7 +108,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
/// ///
/// [`Radio`]: struct.Radio.html /// [`Radio`]: struct.Radio.html
pub fn text_size(mut self, text_size: u16) -> Self { pub fn text_size(mut self, text_size: u16) -> Self {
self.text_size = text_size; self.text_size = Some(text_size);
self self
} }
@ -151,7 +151,7 @@ where
.push( .push(
Text::new(&self.label) Text::new(&self.label)
.width(self.width) .width(self.width)
.size(self.text_size), .size(self.text_size.unwrap_or(renderer.default_size())),
) )
.layout(renderer, limits) .layout(renderer, limits)
} }
@ -194,7 +194,7 @@ where
defaults, defaults,
label_layout.bounds(), label_layout.bounds(),
&self.label, &self.label,
self.text_size, self.text_size.unwrap_or(renderer.default_size()),
Default::default(), Default::default(),
None, None,
HorizontalAlignment::Left, HorizontalAlignment::Left,

View File

@ -16,13 +16,16 @@ use std::{hash::Hash, ops::RangeInclusive};
/// ///
/// A [`Slider`] will try to fill the horizontal space of its container. /// 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 /// [`Slider`]: struct.Slider.html
/// ///
/// # Example /// # Example
/// ``` /// ```
/// # use iced_native::{slider, renderer::Null}; /// # 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 { /// pub enum Message {
/// SliderChanged(f32), /// 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) /// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)] #[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, state: &'a mut State,
range: RangeInclusive<f32>, range: RangeInclusive<T>,
value: f32, step: T,
on_change: Box<dyn Fn(f32) -> Message>, value: T,
on_change: Box<dyn Fn(T) -> Message>,
on_release: Option<Message>, on_release: Option<Message>,
width: Length, width: Length,
style: Renderer::Style, 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`]. /// Creates a new [`Slider`].
/// ///
/// It expects: /// It expects:
@ -60,17 +68,30 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
/// [`State`]: struct.State.html /// [`State`]: struct.State.html
pub fn new<F>( pub fn new<F>(
state: &'a mut State, state: &'a mut State,
range: RangeInclusive<f32>, range: RangeInclusive<T>,
value: f32, value: T,
on_change: F, on_change: F,
) -> Self ) -> Self
where 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 { Slider {
state, state,
value: value.max(*range.start()).min(*range.end()), value,
range, range,
step: T::from(1),
on_change: Box::new(on_change), on_change: Box::new(on_change),
on_release: None, on_release: None,
width: Length::Fill, width: Length::Fill,
@ -106,6 +127,14 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {
self.style = style.into(); self.style = style.into();
self 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`]. /// The local state of a [`Slider`].
@ -125,9 +154,10 @@ impl State {
} }
} }
impl<'a, Message, Renderer> Widget<Message, Renderer> impl<'a, T, Message, Renderer> Widget<Message, Renderer>
for Slider<'a, Message, Renderer> for Slider<'a, T, Message, Renderer>
where where
T: Copy + Into<f64> + num_traits::FromPrimitive,
Renderer: self::Renderer, Renderer: self::Renderer,
Message: Clone, Message: Clone,
{ {
@ -164,17 +194,24 @@ where
) { ) {
let mut change = || { let mut change = || {
let bounds = layout.bounds(); let bounds = layout.bounds();
if cursor_position.x <= bounds.x { if cursor_position.x <= bounds.x {
messages.push((self.on_change)(*self.range.start())); messages.push((self.on_change)(*self.range.start()));
} else if cursor_position.x >= bounds.x + bounds.width { } else if cursor_position.x >= bounds.x + bounds.width {
messages.push((self.on_change)(*self.range.end())); messages.push((self.on_change)(*self.range.end()));
} else { } else {
let percent = (cursor_position.x - bounds.x) / bounds.width; let step = self.step.into();
let value = (self.range.end() - self.range.start()) * percent let start = (*self.range.start()).into();
+ self.range.start(); let end = (*self.range.end()).into();
messages.push((self.on_change)(value)); 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;
if let Some(value) = T::from_f64(value) {
messages.push((self.on_change)(value));
}
} }
}; };
@ -212,11 +249,14 @@ where
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
) -> Renderer::Output { ) -> Renderer::Output {
let start = *self.range.start();
let end = *self.range.end();
renderer.draw( renderer.draw(
layout.bounds(), layout.bounds(),
cursor_position, cursor_position,
self.range.clone(), start.into() as f32..=end.into() as f32,
self.value, self.value.into() as f32,
self.state.is_dragging, self.state.is_dragging,
&self.style, &self.style,
) )
@ -269,14 +309,15 @@ pub trait Renderer: crate::Renderer {
) -> Self::Output; ) -> 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> for Element<'a, Message, Renderer>
where where
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
Renderer: 'a + self::Renderer, Renderer: 'a + self::Renderer,
Message: 'a + Clone, Message: 'a + Clone,
{ {
fn from( fn from(
slider: Slider<'a, Message, Renderer>, slider: Slider<'a, T, Message, Renderer>,
) -> Element<'a, Message, Renderer> { ) -> Element<'a, Message, Renderer> {
Element::new(slider) Element::new(slider)
} }

View File

@ -131,7 +131,7 @@ where
) -> layout::Node { ) -> layout::Node {
let limits = limits.width(self.width).height(self.height); 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(); let bounds = limits.max();
@ -154,7 +154,7 @@ where
defaults, defaults,
layout.bounds(), layout.bounds(),
&self.content, &self.content,
self.size.unwrap_or(Renderer::DEFAULT_SIZE), self.size.unwrap_or(renderer.default_size()),
self.font, self.font,
self.color, self.color,
self.horizontal_alignment, self.horizontal_alignment,
@ -187,10 +187,10 @@ pub trait Renderer: crate::Renderer {
/// [`Text`]: struct.Text.html /// [`Text`]: struct.Text.html
type Font: Default + Copy; type Font: Default + Copy;
/// The default size of [`Text`]. /// Returns the default size of [`Text`].
/// ///
/// [`Text`]: struct.Text.html /// [`Text`]: struct.Text.html
const DEFAULT_SIZE: u16; fn default_size(&self) -> u16;
/// Measures the [`Text`] in the given bounds and returns the minimum /// Measures the [`Text`] in the given bounds and returns the minimum
/// boundaries that can fit the contents. /// boundaries that can fit the contents.

View File

@ -17,8 +17,8 @@ use editor::Editor;
use crate::{ use crate::{
keyboard, layout, keyboard, layout,
mouse::{self, click}, mouse::{self, click},
Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, text, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle,
Widget, Size, Widget,
}; };
use std::u32; use std::u32;
@ -486,7 +486,8 @@ where
let text_bounds = layout.children().next().unwrap().bounds(); let text_bounds = layout.children().next().unwrap().bounds();
if self.is_secure { if self.is_secure {
renderer.draw( self::Renderer::draw(
renderer,
bounds, bounds,
text_bounds, text_bounds,
cursor_position, cursor_position,
@ -498,7 +499,8 @@ where
&self.style, &self.style,
) )
} else { } else {
renderer.draw( self::Renderer::draw(
renderer,
bounds, bounds,
text_bounds, text_bounds,
cursor_position, cursor_position,
@ -531,20 +533,10 @@ where
/// ///
/// [`TextInput`]: struct.TextInput.html /// [`TextInput`]: struct.TextInput.html
/// [renderer]: ../../renderer/index.html /// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer + Sized { pub trait Renderer: text::Renderer + Sized {
/// The font type used for [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
type Font: Default + Copy;
/// The style supported by this renderer. /// The style supported by this renderer.
type Style: Default; 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`]. /// Returns the width of the value of the [`TextInput`].
/// ///
/// [`TextInput`]: struct.TextInput.html /// [`TextInput`]: struct.TextInput.html
@ -683,6 +675,30 @@ impl State {
pub fn cursor(&self) -> Cursor { pub fn cursor(&self) -> 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 // TODO: Reduce allocations

View File

@ -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. /// An interactive cross-platform application.
/// ///
@ -174,6 +176,31 @@ pub trait Application: Sized {
window::Mode::Windowed 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`]. /// Runs the [`Application`].
/// ///
/// On native platforms, this method will take control of the current thread /// 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 { let renderer_settings = crate::renderer::Settings {
default_font: settings.default_font, default_font: settings.default_font,
default_text_size: settings.default_text_size,
antialiasing: if settings.antialiasing { antialiasing: if settings.antialiasing {
Some(crate::renderer::settings::Antialiasing::MSAAx4) Some(crate::renderer::settings::Antialiasing::MSAAx4)
} else { } else {
@ -256,6 +284,14 @@ where
fn subscription(&self) -> Subscription<Self::Message> { fn subscription(&self) -> Subscription<Self::Message> {
self.0.subscription() 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")] #[cfg(target_arch = "wasm32")]

View File

@ -1,4 +1,6 @@
use crate::{executor, Application, Command, Element, Settings, Subscription}; use crate::{
executor, Application, Color, Command, Element, Settings, Subscription,
};
/// A sandboxed [`Application`]. /// A sandboxed [`Application`].
/// ///
@ -124,6 +126,31 @@ pub trait Sandbox {
/// [`Sandbox`]: trait.Sandbox.html /// [`Sandbox`]: trait.Sandbox.html
fn view(&mut self) -> Element<'_, Self::Message>; 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`]. /// Runs the [`Sandbox`].
/// ///
/// On native platforms, this method will take control of the current thread /// On native platforms, this method will take control of the current thread
@ -169,4 +196,12 @@ where
fn view(&mut self) -> Element<'_, T::Message> { fn view(&mut self) -> Element<'_, T::Message> {
T::view(self) T::view(self)
} }
fn background_color(&self) -> Color {
T::background_color(self)
}
fn scale_factor(&self) -> f64 {
T::scale_factor(self)
}
} }

View File

@ -2,7 +2,7 @@
use crate::window; use crate::window;
/// The settings of an application. /// The settings of an application.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone)]
pub struct Settings<Flags> { pub struct Settings<Flags> {
/// The window settings. /// The window settings.
/// ///
@ -22,6 +22,11 @@ pub struct Settings<Flags> {
// TODO: Add `name` for web compatibility // TODO: Add `name` for web compatibility
pub default_font: Option<&'static [u8]>, 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 /// If set to true, the renderer will try to perform antialiasing for some
/// primitives. /// primitives.
/// ///
@ -39,12 +44,28 @@ impl<Flags> Settings<Flags> {
/// ///
/// [`Application`]: ../trait.Application.html /// [`Application`]: ../trait.Application.html
pub fn with_flags(flags: Flags) -> Self { pub fn with_flags(flags: Flags) -> Self {
let default_settings = Settings::<()>::default();
Self { Self {
flags, flags,
// not using ..Default::default() struct update syntax since it is more permissive to antialiasing: default_settings.antialiasing,
// allow initializing with flags without trait bound on Default 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(), antialiasing: Default::default(),
default_font: Default::default(), default_font: Default::default(),
default_text_size: 20,
window: Default::default(), window: Default::default(),
} }
} }
@ -54,12 +75,7 @@ impl<Flags> Settings<Flags> {
impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> { impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> {
fn from(settings: Settings<Flags>) -> iced_winit::Settings<Flags> { fn from(settings: Settings<Flags>) -> iced_winit::Settings<Flags> {
iced_winit::Settings { iced_winit::Settings {
window: iced_winit::settings::Window { window: settings.window.into(),
size: settings.window.size,
resizable: settings.window.resizable,
decorations: settings.window.decorations,
platform_specific: Default::default(),
},
flags: settings.flags, flags: settings.flags,
} }
} }

View File

@ -50,7 +50,7 @@ mod platform {
text_input::TextInput, text_input::TextInput,
}; };
#[cfg(feature = "canvas")] #[cfg(any(feature = "canvas", feature = "glow_canvas"))]
#[doc(no_inline)] #[doc(no_inline)]
pub use canvas::Canvas; pub use canvas::Canvas;
} }

View File

@ -2,5 +2,8 @@
mod mode; mod mode;
mod settings; mod settings;
pub mod icon;
pub use icon::Icon;
pub use mode::Mode; pub use mode::Mode;
pub use settings::Settings; pub use settings::Settings;

132
src/window/icon.rs Normal file
View File

@ -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)
}
}

View File

@ -1,22 +1,51 @@
use crate::window::Icon;
/// The window settings of an application. /// The window settings of an application.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone)]
pub struct Settings { pub struct Settings {
/// The size of the window. /// The initial size of the window.
pub size: (u32, u32), 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. /// Whether the window should be resizable or not.
pub resizable: bool, pub resizable: bool,
/// Whether the window should have a border, a title bar, etc. or not. /// Whether the window should have a border, a title bar, etc. or not.
pub decorations: bool, pub decorations: bool,
/// The icon of the window.
pub icon: Option<Icon>,
} }
impl Default for Settings { impl Default for Settings {
fn default() -> Settings { fn default() -> Settings {
Settings { Settings {
size: (1024, 768), size: (1024, 768),
min_size: None,
max_size: None,
resizable: true, resizable: true,
decorations: 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(),
} }
} }
} }

View File

@ -2,6 +2,8 @@
//! //!
//! It contains a set of styles and stylesheets for most of the built-in //! It contains a set of styles and stylesheets for most of the built-in
//! widgets. //! widgets.
pub use iced_core::{Background, Color};
pub mod button; pub mod button;
pub mod checkbox; pub mod checkbox;
pub mod container; pub mod container;

View File

@ -15,10 +15,11 @@ categories = ["web-programming"]
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
dodrio = "0.1.0" dodrio = "0.2"
wasm-bindgen = "0.2.51" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
url = "2.0" url = "2.0"
num-traits = "0.2"
[dependencies.iced_core] [dependencies.iced_core]
version = "0.2" version = "0.2"

View File

@ -238,28 +238,25 @@ struct Instance<A: Application> {
bus: Bus<A::Message>, bus: Bus<A::Message>,
} }
impl<A> dodrio::Render for Instance<A> impl<'a, A> dodrio::Render<'a> for Instance<A>
where where
A: Application, A: Application,
{ {
fn render<'a, 'bump>( fn render(
&'a self, &self,
bump: &'bump bumpalo::Bump, context: &mut dodrio::RenderContext<'a>,
) -> dodrio::Node<'bump> ) -> dodrio::Node<'a> {
where
'a: 'bump,
{
use dodrio::builder::*; use dodrio::builder::*;
let mut ui = self.application.borrow_mut(); let mut ui = self.application.borrow_mut();
let element = ui.view(); let element = ui.view();
let mut css = Css::new(); 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%") .attr("style", "width: 100%; height: 100%")
.children(vec![css.node(bump), node]) .children(vec![css.node(context.bump), node])
.finish() .finish()
} }
} }

View File

@ -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) let mut node = button(bump)
.attr( .attr("class", class)
"class",
bumpalo::format!(in bump, "{}", padding_class).into_bump_str(),
)
.attr( .attr(
"style", "style",
bumpalo::format!( bumpalo::format!(
in bump, in bump,
"background: {}; border-radius: {}px; width:{}; min-width: {}; color: {}", "background: {}; border-radius: {}px; width:{}; \
min-width: {}; color: {}",
background, background,
style.border_radius, style.border_radius,
css::length(self.width), css::length(self.width),

View File

@ -28,6 +28,7 @@ pub struct Checkbox<Message> {
is_checked: bool, is_checked: bool,
on_toggle: Rc<dyn Fn(bool) -> Message>, on_toggle: Rc<dyn Fn(bool) -> Message>,
label: String, label: String,
id: Option<String>,
width: Length, width: Length,
style: Box<dyn StyleSheet>, style: Box<dyn StyleSheet>,
} }
@ -51,6 +52,7 @@ impl<Message> Checkbox<Message> {
is_checked, is_checked,
on_toggle: Rc::new(f), on_toggle: Rc::new(f),
label: label.into(), label: label.into(),
id: None,
width: Length::Shrink, width: Length::Shrink,
style: Default::default(), style: Default::default(),
} }
@ -71,6 +73,14 @@ impl<Message> Checkbox<Message> {
self.style = style.into(); self.style = style.into();
self 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> impl<Message> Widget<Message> for Checkbox<Message>
@ -84,8 +94,10 @@ where
style_sheet: &mut Css<'b>, style_sheet: &mut Css<'b>,
) -> dodrio::Node<'b> { ) -> dodrio::Node<'b> {
use dodrio::builder::*; 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 event_bus = bus.clone();
let on_toggle = self.on_toggle.clone(); let on_toggle = self.on_toggle.clone();
@ -95,7 +107,15 @@ where
let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(5)); 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( .attr(
"class", "class",
bumpalo::format!(in bump, "{} {}", row_class, spacing_class) bumpalo::format!(in bump, "{} {}", row_class, spacing_class)
@ -108,7 +128,7 @@ where
) )
.children(vec![ .children(vec![
// TODO: Checkbox styling // TODO: Checkbox styling
input(bump) input
.attr("type", "checkbox") .attr("type", "checkbox")
.bool_attr("checked", self.is_checked) .bool_attr("checked", self.is_checked)
.on("click", move |_root, vdom, _event| { .on("click", move |_root, vdom, _event| {
@ -118,8 +138,7 @@ where
vdom.schedule_render(); vdom.schedule_render();
}) })
.finish(), .finish(),
span(bump).children(vec![ text(checkbox_label),
text(checkbox_label.into_bump_str())]).finish(),
]) ])
.finish() .finish()
} }

View File

@ -22,6 +22,9 @@ pub struct Image {
/// The image path /// The image path
pub handle: Handle, pub handle: Handle,
/// The alt text of the image
pub alt: String,
/// The width of the image /// The width of the image
pub width: Length, pub width: Length,
@ -36,6 +39,7 @@ impl Image {
pub fn new<T: Into<Handle>>(handle: T) -> Self { pub fn new<T: Into<Handle>>(handle: T) -> Self {
Image { Image {
handle: handle.into(), handle: handle.into(),
alt: Default::default(),
width: Length::Shrink, width: Length::Shrink,
height: Length::Shrink, height: Length::Shrink,
} }
@ -56,6 +60,14 @@ impl Image {
self.height = height; self.height = height;
self 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 { impl<Message> Widget<Message> for Image {
@ -66,12 +78,19 @@ impl<Message> Widget<Message> for Image {
_style_sheet: &mut Css<'b>, _style_sheet: &mut Css<'b>,
) -> dodrio::Node<'b> { ) -> dodrio::Node<'b> {
use dodrio::builder::*; use dodrio::builder::*;
use dodrio::bumpalo::collections::String;
let src = bumpalo::format!(in bump, "{}", match self.handle.data.as_ref() { let src = String::from_str_in(
Data::Path(path) => path.to_str().unwrap_or("") match self.handle.data.as_ref() {
}); Data::Path(path) => path.to_str().unwrap_or(""),
},
bump,
)
.into_bump_str();
let mut image = img(bump).attr("src", src.into_bump_str()); let alt = String::from_str_in(&self.alt, bump).into_bump_str();
let mut image = img(bump).attr("src", src).attr("alt", alt);
match self.width { match self.width {
Length::Shrink => {} Length::Shrink => {}

View File

@ -35,6 +35,8 @@ pub struct Radio<Message> {
is_selected: bool, is_selected: bool,
on_click: Message, on_click: Message,
label: String, label: String,
id: Option<String>,
name: Option<String>,
style: Box<dyn StyleSheet>, style: Box<dyn StyleSheet>,
} }
@ -63,6 +65,8 @@ impl<Message> Radio<Message> {
is_selected: Some(value) == selected, is_selected: Some(value) == selected,
on_click: f(value), on_click: f(value),
label: label.into(), label: label.into(),
id: None,
name: None,
style: Default::default(), style: Default::default(),
} }
} }
@ -74,6 +78,22 @@ impl<Message> Radio<Message> {
self.style = style.into(); self.style = style.into();
self 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> impl<Message> Widget<Message> for Radio<Message>
@ -87,17 +107,35 @@ where
_style_sheet: &mut Css<'b>, _style_sheet: &mut Css<'b>,
) -> dodrio::Node<'b> { ) -> dodrio::Node<'b> {
use dodrio::builder::*; 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 event_bus = bus.clone();
let on_click = self.on_click.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 // TODO: Complete styling
label(bump) label
.attr("style", "display: block; font-size: 20px") .attr("style", "display: block; font-size: 20px")
.children(vec![ .children(vec![
input(bump) input
.attr("type", "radio") .attr("type", "radio")
.attr("style", "margin-right: 10px") .attr("style", "margin-right: 10px")
.bool_attr("checked", self.is_selected) .bool_attr("checked", self.is_selected)
@ -105,7 +143,7 @@ where
event_bus.publish(on_click.clone()); event_bus.publish(on_click.clone());
}) })
.finish(), .finish(),
text(radio_label.into_bump_str()), text(radio_label),
]) ])
.finish() .finish()
} }

View File

@ -16,6 +16,9 @@ use std::{ops::RangeInclusive, rc::Rc};
/// ///
/// A [`Slider`] will try to fill the horizontal space of its container. /// 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 /// [`Slider`]: struct.Slider.html
/// ///
/// # Example /// # 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) /// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Slider<'a, Message> { pub struct Slider<'a, T, Message> {
_state: &'a mut State, _state: &'a mut State,
range: RangeInclusive<f32>, range: RangeInclusive<T>,
value: f32, step: T,
on_change: Rc<Box<dyn Fn(f32) -> Message>>, value: T,
on_change: Rc<Box<dyn Fn(T) -> Message>>,
width: Length, width: Length,
style: Box<dyn StyleSheet>, 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`]. /// Creates a new [`Slider`].
/// ///
/// It expects: /// It expects:
@ -58,17 +65,30 @@ impl<'a, Message> Slider<'a, Message> {
/// [`State`]: struct.State.html /// [`State`]: struct.State.html
pub fn new<F>( pub fn new<F>(
state: &'a mut State, state: &'a mut State,
range: RangeInclusive<f32>, range: RangeInclusive<T>,
value: f32, value: T,
on_change: F, on_change: F,
) -> Self ) -> Self
where 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 { Slider {
_state: state, _state: state,
value: value.max(*range.start()).min(*range.end()), value,
range, range,
step: T::from(1),
on_change: Rc::new(Box::new(on_change)), on_change: Rc::new(Box::new(on_change)),
width: Length::Fill, width: Length::Fill,
style: Default::default(), style: Default::default(),
@ -90,10 +110,19 @@ impl<'a, Message> Slider<'a, Message> {
self.style = style.into(); self.style = style.into();
self 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 where
T: 'static + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'static, Message: 'static,
{ {
fn node<'b>( fn node<'b>(
@ -107,18 +136,18 @@ where
let (start, end) = self.range.clone().into_inner(); let (start, end) = self.range.clone().into_inner();
let min = bumpalo::format!(in bump, "{}", start); let min = bumpalo::format!(in bump, "{}", start.into());
let max = bumpalo::format!(in bump, "{}", end); let max = bumpalo::format!(in bump, "{}", end.into());
let value = bumpalo::format!(in bump, "{}", self.value); 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 on_change = self.on_change.clone();
let event_bus = bus.clone(); let event_bus = bus.clone();
// TODO: Make `step` configurable
// TODO: Styling // TODO: Styling
input(bump) input(bump)
.attr("type", "range") .attr("type", "range")
.attr("step", "0.01") .attr("step", step.into_bump_str())
.attr("min", min.into_bump_str()) .attr("min", min.into_bump_str())
.attr("max", max.into_bump_str()) .attr("max", max.into_bump_str())
.attr("value", value.into_bump_str()) .attr("value", value.into_bump_str())
@ -131,19 +160,22 @@ where
Some(slider) => slider, Some(slider) => slider,
}; };
if let Ok(value) = slider.value().parse::<f32>() { if let Ok(value) = slider.value().parse::<f64>() {
event_bus.publish(on_change(value)); if let Some(value) = T::from_f64(value) {
event_bus.publish(on_change(value));
}
} }
}) })
.finish() .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 where
T: 'static + Copy + Into<f64> + num_traits::FromPrimitive,
Message: 'static, Message: 'static,
{ {
fn from(slider: Slider<'a, Message>) -> Element<'a, Message> { fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message> {
Element::new(slider) Element::new(slider)
} }
} }

View File

@ -116,7 +116,12 @@ impl<'a, Message> Widget<Message> for Text {
) -> dodrio::Node<'b> { ) -> dodrio::Node<'b> {
use dodrio::builder::*; 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 let color = self
.color .color
.map(css::color) .map(css::color)
@ -133,7 +138,8 @@ impl<'a, Message> Widget<Message> for Text {
let style = bumpalo::format!( let style = bumpalo::format!(
in bump, in bump,
"width: {}; height: {}; font-size: {}px; color: {}; text-align: {}; font-family: {}", "width: {}; height: {}; font-size: {}px; color: {}; \
text-align: {}; font-family: {}",
width, width,
height, height,
self.size.unwrap_or(20), self.size.unwrap_or(20),

View File

@ -151,8 +151,26 @@ where
use dodrio::builder::*; use dodrio::builder::*;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
let padding_class = let class = {
style_sheet.insert(bump, css::Rule::Padding(self.padding)); 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_change = self.on_change.clone();
let on_submit = self.on_submit.clone(); let on_submit = self.on_submit.clone();
@ -161,15 +179,14 @@ where
let style = self.style_sheet.active(); let style = self.style_sheet.active();
input(bump) input(bump)
.attr( .attr("class", class)
"class",
bumpalo::format!(in bump, "{}", padding_class).into_bump_str(),
)
.attr( .attr(
"style", "style",
bumpalo::format!( bumpalo::format!(
in bump, 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::length(self.width),
css::max_length(self.max_width), css::max_length(self.max_width),
self.size.unwrap_or(20), self.size.unwrap_or(20),
@ -181,19 +198,9 @@ where
) )
.into_bump_str(), .into_bump_str(),
) )
.attr( .attr("placeholder", placeholder)
"placeholder", .attr("value", value)
bumpalo::format!(in bump, "{}", self.placeholder) .attr("type", if self.is_secure { "password" } else { "text" })
.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(),
)
.on("input", move |_root, _vdom, event| { .on("input", move |_root, _vdom, event| {
let text_input = match event.target().and_then(|t| { let text_input = match event.target().and_then(|t| {
t.dyn_into::<web_sys::HtmlInputElement>().ok() t.dyn_into::<web_sys::HtmlInputElement>().ok()
@ -206,10 +213,13 @@ where
}) })
.on("keypress", move |_root, _vdom, event| { .on("keypress", move |_root, _vdom, event| {
if let Some(on_submit) = on_submit.clone() { 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() { match event.key_code() {
13 => { submit_event_bus.publish(on_submit); } 13 => {
submit_event_bus.publish(on_submit);
}
_ => {} _ => {}
} }
} }

View File

@ -24,6 +24,8 @@ pub struct Backend {
#[cfg(any(feature = "image", feature = "svg"))] #[cfg(any(feature = "image", feature = "svg"))]
image_pipeline: image::Pipeline, image_pipeline: image::Pipeline,
default_text_size: u16,
} }
impl Backend { impl Backend {
@ -50,6 +52,8 @@ impl Backend {
#[cfg(any(feature = "image", feature = "svg"))] #[cfg(any(feature = "image", feature = "svg"))]
image_pipeline, 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 ICON_FONT: Font = font::ICONS;
const CHECKMARK_ICON: char = font::CHECKMARK_ICON; const CHECKMARK_ICON: char = font::CHECKMARK_ICON;
fn default_size(&self) -> u16 {
self.default_text_size
}
fn measure( fn measure(
&self, &self,
contents: &str, contents: &str,

View File

@ -36,7 +36,7 @@ mod backend;
mod quad; mod quad;
mod text; mod text;
pub use iced_graphics::{Antialiasing, Defaults, Primitive, Viewport}; pub use iced_graphics::{Antialiasing, Color, Defaults, Primitive, Viewport};
pub use wgpu; pub use wgpu;
pub use backend::Backend; pub use backend::Backend;

View File

@ -16,6 +16,11 @@ pub struct Settings {
/// If `None` is provided, a default system font will be chosen. /// If `None` is provided, a default system font will be chosen.
pub default_font: Option<&'static [u8]>, 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. /// The antialiasing strategy that will be used for triangle primitives.
pub antialiasing: Option<Antialiasing>, pub antialiasing: Option<Antialiasing>,
} }
@ -25,6 +30,7 @@ impl Default for Settings {
Settings { Settings {
format: wgpu::TextureFormat::Bgra8UnormSrgb, format: wgpu::TextureFormat::Bgra8UnormSrgb,
default_font: None, default_font: None,
default_text_size: 20,
antialiasing: None, antialiasing: None,
} }
} }

View File

@ -16,6 +16,7 @@ const INDEX_BUFFER_SIZE: usize = 10_000;
pub(crate) struct Pipeline { pub(crate) struct Pipeline {
pipeline: wgpu::RenderPipeline, pipeline: wgpu::RenderPipeline,
blit: Option<msaa::Blit>, blit: Option<msaa::Blit>,
constants_layout: wgpu::BindGroupLayout,
constants: wgpu::BindGroup, constants: wgpu::BindGroup,
uniforms_buffer: Buffer<Uniforms>, uniforms_buffer: Buffer<Uniforms>,
vertex_buffer: Buffer<Vertex2D>, vertex_buffer: Buffer<Vertex2D>,
@ -50,8 +51,10 @@ impl<T> Buffer<T> {
} }
} }
pub fn ensure_capacity(&mut self, device: &wgpu::Device, size: usize) { pub fn expand(&mut self, device: &wgpu::Device, size: usize) -> bool {
if self.size < size { let needs_resize = self.size < size;
if needs_resize {
self.raw = device.create_buffer(&wgpu::BufferDescriptor { self.raw = device.create_buffer(&wgpu::BufferDescriptor {
label: None, label: None,
size: (std::mem::size_of::<T>() * size) as u64, size: (std::mem::size_of::<T>() * size) as u64,
@ -60,6 +63,8 @@ impl<T> Buffer<T> {
self.size = size; self.size = size;
} }
needs_resize
} }
} }
@ -69,7 +74,7 @@ impl Pipeline {
format: wgpu::TextureFormat, format: wgpu::TextureFormat,
antialiasing: Option<settings::Antialiasing>, antialiasing: Option<settings::Antialiasing>,
) -> Pipeline { ) -> Pipeline {
let constant_layout = let constants_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None, label: None,
bindings: &[wgpu::BindGroupLayoutEntry { bindings: &[wgpu::BindGroupLayoutEntry {
@ -88,7 +93,7 @@ impl Pipeline {
let constant_bind_group = let constant_bind_group =
device.create_bind_group(&wgpu::BindGroupDescriptor { device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None, label: None,
layout: &constant_layout, layout: &constants_layout,
bindings: &[wgpu::Binding { bindings: &[wgpu::Binding {
binding: 0, binding: 0,
resource: wgpu::BindingResource::Buffer { resource: wgpu::BindingResource::Buffer {
@ -100,7 +105,7 @@ impl Pipeline {
let layout = let layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
bind_group_layouts: &[&constant_layout], bind_group_layouts: &[&constants_layout],
}); });
let vs = include_bytes!("shader/triangle.vert.spv"); let vs = include_bytes!("shader/triangle.vert.spv");
@ -180,6 +185,7 @@ impl Pipeline {
Pipeline { Pipeline {
pipeline, pipeline,
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
constants_layout,
constants: constant_bind_group, constants: constant_bind_group,
uniforms_buffer: constants_buffer, uniforms_buffer: constants_buffer,
vertex_buffer: Buffer::new( vertex_buffer: Buffer::new(
@ -220,9 +226,25 @@ impl Pipeline {
// Then we ensure the current buffers are big enough, resizing if // Then we ensure the current buffers are big enough, resizing if
// necessary // necessary
self.uniforms_buffer.ensure_capacity(device, meshes.len()); let _ = self.vertex_buffer.expand(device, total_vertices);
self.vertex_buffer.ensure_capacity(device, total_vertices); let _ = self.index_buffer.expand(device, total_indices);
self.index_buffer.ensure_capacity(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 uniforms: Vec<Uniforms> = Vec::with_capacity(meshes.len());
let mut offsets: Vec<( let mut offsets: Vec<(

View File

@ -13,4 +13,4 @@ pub use iced_native::slider::State;
/// values. /// values.
/// ///
/// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. /// 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>;

View File

@ -1,4 +1,4 @@
use crate::{Backend, Renderer, Settings}; use crate::{Backend, Color, Renderer, Settings};
use iced_graphics::Viewport; use iced_graphics::Viewport;
use iced_native::{futures, mouse}; use iced_native::{futures, mouse};
@ -103,6 +103,7 @@ impl iced_graphics::window::Compositor for Compositor {
renderer: &mut Self::Renderer, renderer: &mut Self::Renderer,
swap_chain: &mut Self::SwapChain, swap_chain: &mut Self::SwapChain,
viewport: &Viewport, viewport: &Viewport,
background_color: Color,
output: &<Self::Renderer as iced_native::Renderer>::Output, output: &<Self::Renderer as iced_native::Renderer>::Output,
overlay: &[T], overlay: &[T],
) -> mouse::Interaction { ) -> mouse::Interaction {
@ -118,11 +119,15 @@ impl iced_graphics::window::Compositor for Compositor {
resolve_target: None, resolve_target: None,
load_op: wgpu::LoadOp::Clear, load_op: wgpu::LoadOp::Clear,
store_op: wgpu::StoreOp::Store, store_op: wgpu::StoreOp::Store,
clear_color: wgpu::Color { clear_color: {
r: 1.0, let [r, g, b, a] = background_color.into_linear();
g: 1.0,
b: 1.0, wgpu::Color {
a: 1.0, r: f64::from(r),
g: f64::from(g),
b: f64::from(b),
a: f64::from(a),
}
}, },
}], }],
depth_stencil_attachment: None, depth_stencil_attachment: None,

View File

@ -1,6 +1,6 @@
//! Create interactive, native cross-platform applications. //! Create interactive, native cross-platform applications.
use crate::{ use crate::{
conversion, mouse, Clipboard, Command, Debug, Executor, Mode, Proxy, conversion, mouse, Clipboard, Color, Command, Debug, Executor, Mode, Proxy,
Runtime, Settings, Size, Subscription, Runtime, Settings, Size, Subscription,
}; };
use iced_graphics::window; use iced_graphics::window;
@ -73,6 +73,32 @@ pub trait Application: Program {
fn mode(&self) -> Mode { fn mode(&self) -> Mode {
Mode::Windowed 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 /// 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 title = application.title();
let mut mode = application.mode(); let mut mode = application.mode();
let mut background_color = application.background_color();
let mut scale_factor = application.scale_factor();
let window = settings let window = settings
.window .window
@ -120,13 +148,14 @@ pub fn run<A, E, C>(
.expect("Open window"); .expect("Open window");
let clipboard = Clipboard::new(&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 mouse_interaction = mouse::Interaction::default();
let mut modifiers = winit::event::ModifiersState::default(); let mut modifiers = winit::event::ModifiersState::default();
let physical_size = window.inner_size(); let physical_size = window.inner_size();
let mut viewport = Viewport::with_physical_size( let mut viewport = Viewport::with_physical_size(
Size::new(physical_size.width, physical_size.height), Size::new(physical_size.width, physical_size.height),
window.scale_factor(), window.scale_factor() * scale_factor,
); );
let mut resized = false; let mut resized = false;
@ -143,6 +172,7 @@ pub fn run<A, E, C>(
let mut state = program::State::new( let mut state = program::State::new(
application, application,
viewport.logical_size(), viewport.logical_size(),
conversion::cursor_position(cursor_position, viewport.scale_factor()),
&mut renderer, &mut renderer,
&mut debug, &mut debug,
); );
@ -150,10 +180,18 @@ pub fn run<A, E, C>(
event_loop.run(move |event, _, control_flow| match event { event_loop.run(move |event, _, control_flow| match event {
event::Event::MainEventsCleared => { event::Event::MainEventsCleared => {
if state.is_queue_empty() {
return;
}
let command = runtime.enter(|| { let command = runtime.enter(|| {
state.update( state.update(
clipboard.as_ref().map(|c| c as _),
viewport.logical_size(), viewport.logical_size(),
conversion::cursor_position(
cursor_position,
viewport.scale_factor(),
),
clipboard.as_ref().map(|c| c as _),
&mut renderer, &mut renderer,
&mut debug, &mut debug,
) )
@ -189,6 +227,39 @@ pub fn run<A, E, C>(
mode = new_mode; 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(); window.request_redraw();
@ -215,6 +286,7 @@ pub fn run<A, E, C>(
&mut renderer, &mut renderer,
&mut swap_chain, &mut swap_chain,
&viewport, &viewport,
background_color,
state.primitive(), state.primitive(),
&debug.overlay(), &debug.overlay(),
); );
@ -239,7 +311,9 @@ pub fn run<A, E, C>(
handle_window_event( handle_window_event(
&window_event, &window_event,
&window, &window,
scale_factor,
control_flow, control_flow,
&mut cursor_position,
&mut modifiers, &mut modifiers,
&mut viewport, &mut viewport,
&mut resized, &mut resized,
@ -266,7 +340,9 @@ pub fn run<A, E, C>(
pub fn handle_window_event( pub fn handle_window_event(
event: &winit::event::WindowEvent<'_>, event: &winit::event::WindowEvent<'_>,
window: &winit::window::Window, window: &winit::window::Window,
scale_factor: f64,
control_flow: &mut winit::event_loop::ControlFlow, control_flow: &mut winit::event_loop::ControlFlow,
cursor_position: &mut winit::dpi::PhysicalPosition<f64>,
modifiers: &mut winit::event::ModifiersState, modifiers: &mut winit::event::ModifiersState,
viewport: &mut Viewport, viewport: &mut Viewport,
resized: &mut bool, resized: &mut bool,
@ -278,13 +354,18 @@ pub fn handle_window_event(
WindowEvent::Resized(new_size) => { WindowEvent::Resized(new_size) => {
let size = Size::new(new_size.width, new_size.height); let size = Size::new(new_size.width, new_size.height);
*viewport = *viewport = Viewport::with_physical_size(
Viewport::with_physical_size(size, window.scale_factor()); size,
window.scale_factor() * scale_factor,
);
*resized = true; *resized = true;
} }
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
} }
WindowEvent::CursorMoved { position, .. } => {
*cursor_position = *position;
}
WindowEvent::ModifiersChanged(new_modifiers) => { WindowEvent::ModifiersChanged(new_modifiers) => {
*modifiers = *new_modifiers; *modifiers = *new_modifiers;
} }

View File

@ -4,7 +4,7 @@
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
use crate::{ use crate::{
keyboard::{self, KeyCode, ModifiersState}, keyboard::{self, KeyCode, ModifiersState},
mouse, window, Event, Mode, mouse, window, Event, Mode, Point,
}; };
/// Converts a winit window event into an iced event. /// 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) => { WindowEvent::HoveredFile(path) => {
Some(Event::Window(window::Event::FileHovered(path.clone()))) 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. /// Converts a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code.
/// ///
/// [`winit`]: https://github.com/rust-windowing/winit /// [`winit`]: https://github.com/rust-windowing/winit

View File

@ -14,7 +14,7 @@ use winit::monitor::MonitorHandle;
use winit::window::WindowBuilder; use winit::window::WindowBuilder;
/// The settings of an application. /// The settings of an application.
#[derive(Debug, Clone, Copy, PartialEq, Default)] #[derive(Debug, Clone, Default)]
pub struct Settings<Flags> { pub struct Settings<Flags> {
/// The [`Window`] settings /// The [`Window`] settings
/// ///
@ -28,17 +28,26 @@ pub struct Settings<Flags> {
} }
/// The window settings of an application. /// The window settings of an application.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone)]
pub struct Window { pub struct Window {
/// The size of the window. /// The size of the window.
pub size: (u32, u32), 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. /// Whether the window should be resizable or not.
pub resizable: bool, pub resizable: bool,
/// Whether the window should have a border, a title bar, etc. /// Whether the window should have a border, a title bar, etc.
pub decorations: bool, pub decorations: bool,
/// The window icon, which is also usually used in the taskbar
pub icon: Option<winit::window::Icon>,
/// Platform specific settings. /// Platform specific settings.
pub platform_specific: platform::PlatformSpecific, pub platform_specific: platform::PlatformSpecific,
} }
@ -60,8 +69,19 @@ impl Window {
.with_inner_size(winit::dpi::LogicalSize { width, height }) .with_inner_size(winit::dpi::LogicalSize { width, height })
.with_resizable(self.resizable) .with_resizable(self.resizable)
.with_decorations(self.decorations) .with_decorations(self.decorations)
.with_window_icon(self.icon)
.with_fullscreen(conversion::fullscreen(primary_monitor, mode)); .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")] #[cfg(target_os = "windows")]
{ {
use winit::platform::windows::WindowBuilderExtWindows; use winit::platform::windows::WindowBuilderExtWindows;
@ -79,8 +99,11 @@ impl Default for Window {
fn default() -> Window { fn default() -> Window {
Window { Window {
size: (1024, 768), size: (1024, 768),
min_size: None,
max_size: None,
resizable: true, resizable: true,
decorations: true, decorations: true,
icon: None,
platform_specific: Default::default(), platform_specific: Default::default(),
} }
} }