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