diff --git a/core/src/length.rs b/core/src/length.rs index 63ba6207..10873e89 100644 --- a/core/src/length.rs +++ b/core/src/length.rs @@ -27,3 +27,9 @@ impl Length { } } } + +impl From for Length { + fn from(units: u16) -> Self { + Length::Units(units) + } +} diff --git a/core/src/point.rs b/core/src/point.rs index 183998dd..47c8b142 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -19,6 +19,18 @@ impl Point { } } +impl From<[f32; 2]> for Point { + fn from([x, y]: [f32; 2]) -> Self { + Point { x, y } + } +} + +impl From<[u16; 2]> for Point { + fn from([x, y]: [u16; 2]) -> Self { + Point::new(x.into(), y.into()) + } +} + impl std::ops::Add for Point { type Output = Self; diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 182f033a..da0e5159 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -61,13 +61,13 @@ impl text::Renderer for Null { } impl scrollable::Renderer for Null { - fn is_mouse_over_scrollbar( + fn scrollbar( &self, _bounds: Rectangle, _content_bounds: Rectangle, - _cursor_position: Point, - ) -> bool { - false + _offset: u32, + ) -> Option { + None } fn draw( @@ -77,6 +77,7 @@ impl scrollable::Renderer for Null { _content_bounds: Rectangle, _is_mouse_over: bool, _is_mouse_over_scrollbar: bool, + _scrollbar: Option, _offset: u32, _content: Self::Output, ) { diff --git a/native/src/size.rs b/native/src/size.rs index 30e2a57e..389b3247 100644 --- a/native/src/size.rs +++ b/native/src/size.rs @@ -37,3 +37,15 @@ impl Size { } } } + +impl From<[f32; 2]> for Size { + fn from([width, height]: [f32; 2]) -> Self { + Size { width, height } + } +} + +impl From<[u16; 2]> for Size { + fn from([width, height]: [u16; 2]) -> Self { + Size::new(width.into(), height.into()) + } +} diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 0d73fdb0..7a7138ff 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -102,6 +102,7 @@ where } fn hash_layout(&self, state: &mut Hasher) { + self.path.hash(state); self.width.hash(state); self.height.hash(state); } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 678d837a..3c2625b7 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -150,12 +150,6 @@ where let content = layout.children().next().unwrap(); let content_bounds = content.bounds(); - let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar( - bounds, - content_bounds, - cursor_position, - ); - // TODO: Event capture. Nested scrollables should capture scroll events. if is_mouse_over { match event { @@ -174,49 +168,66 @@ where } } - if self.state.is_scrollbar_grabbed() || is_mouse_over_scrollbar { + let offset = self.state.offset(bounds, content_bounds); + let scrollbar = renderer.scrollbar(bounds, content_bounds, offset); + let is_mouse_over_scrollbar = scrollbar + .as_ref() + .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) + .unwrap_or(false); + + if self.state.is_scroller_grabbed() { match event { Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, - state, - }) => match state { - ButtonState::Pressed => { - self.state.scroll_to( - cursor_position.y / (bounds.y + bounds.height), - bounds, - content_bounds, - ); - - self.state.scrollbar_grabbed_at = Some(cursor_position); - } - ButtonState::Released => { - self.state.scrollbar_grabbed_at = None; - } - }, + state: ButtonState::Released, + }) => { + self.state.scroller_grabbed_at = None; + } Event::Mouse(mouse::Event::CursorMoved { .. }) => { - if let Some(scrollbar_grabbed_at) = - self.state.scrollbar_grabbed_at + if let (Some(scrollbar), Some(scroller_grabbed_at)) = + (scrollbar, self.state.scroller_grabbed_at) { - let ratio = content_bounds.height / bounds.height; - let delta = scrollbar_grabbed_at.y - cursor_position.y; - - self.state.scroll( - delta * ratio, + self.state.scroll_to( + scrollbar.scroll_percentage( + scroller_grabbed_at, + cursor_position, + ), bounds, content_bounds, ); + } + } + _ => {} + } + } else if is_mouse_over_scrollbar { + match event { + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + state: ButtonState::Pressed, + }) => { + if let Some(scrollbar) = scrollbar { + if let Some(scroller_grabbed_at) = + scrollbar.grab_scroller(cursor_position) + { + self.state.scroll_to( + scrollbar.scroll_percentage( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); - self.state.scrollbar_grabbed_at = Some(cursor_position); + self.state.scroller_grabbed_at = + Some(scroller_grabbed_at); + } } } _ => {} } } - let cursor_position = if is_mouse_over - && !(is_mouse_over_scrollbar - || self.state.scrollbar_grabbed_at.is_some()) - { + let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { Point::new( cursor_position.x, cursor_position.y @@ -249,13 +260,13 @@ where let content_layout = layout.children().next().unwrap(); let content_bounds = content_layout.bounds(); let offset = self.state.offset(bounds, content_bounds); + let scrollbar = renderer.scrollbar(bounds, content_bounds, offset); let is_mouse_over = bounds.contains(cursor_position); - let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar( - bounds, - content_bounds, - cursor_position, - ); + let is_mouse_over_scrollbar = scrollbar + .as_ref() + .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) + .unwrap_or(false); let content = { let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { @@ -274,6 +285,7 @@ where content_layout.bounds(), is_mouse_over, is_mouse_over_scrollbar, + scrollbar, offset, content, ) @@ -294,7 +306,7 @@ where /// [`Scrollable`]: struct.Scrollable.html #[derive(Debug, Clone, Copy, Default)] pub struct State { - scrollbar_grabbed_at: Option, + scroller_grabbed_at: Option, offset: f32, } @@ -356,12 +368,68 @@ impl State { self.offset.min(hidden_content as f32) as u32 } - /// Returns whether the scrollbar is currently grabbed or not. - pub fn is_scrollbar_grabbed(&self) -> bool { - self.scrollbar_grabbed_at.is_some() + /// Returns whether the scroller is currently grabbed or not. + pub fn is_scroller_grabbed(&self) -> bool { + self.scroller_grabbed_at.is_some() } } +/// The scrollbar of a [`Scrollable`]. +/// +/// [`Scrollable`]: struct.Scrollable.html +#[derive(Debug)] +pub struct Scrollbar { + /// The bounds of the [`Scrollbar`]. + /// + /// [`Scrollbar`]: struct.Scrollbar.html + pub bounds: Rectangle, + + /// The bounds of the [`Scroller`]. + /// + /// [`Scroller`]: struct.Scroller.html + pub scroller: Scroller, +} + +impl Scrollbar { + fn is_mouse_over(&self, cursor_position: Point) -> bool { + self.bounds.contains(cursor_position) + } + + fn grab_scroller(&self, cursor_position: Point) -> Option { + if self.bounds.contains(cursor_position) { + Some(if self.scroller.bounds.contains(cursor_position) { + (cursor_position.y - self.scroller.bounds.y) + / self.scroller.bounds.height + } else { + 0.5 + }) + } else { + None + } + } + + fn scroll_percentage( + &self, + grabbed_at: f32, + cursor_position: Point, + ) -> f32 { + (cursor_position.y + self.bounds.y + - self.scroller.bounds.height * grabbed_at) + / (self.bounds.height - self.scroller.bounds.height) + } +} + +/// The handle of a [`Scrollbar`]. +/// +/// [`Scrollbar`]: struct.Scrollbar.html +#[derive(Debug, Clone, Copy)] +pub struct Scroller { + /// The bounds of the [`Scroller`]. + /// + /// [`Scroller`]: struct.Scrollbar.html + pub bounds: Rectangle, +} + /// The renderer of a [`Scrollable`]. /// /// Your [renderer] will need to implement this trait before being @@ -370,27 +438,31 @@ impl State { /// [`Scrollable`]: struct.Scrollable.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { - /// Returns whether the mouse is over the scrollbar given the bounds of - /// the [`Scrollable`] and its contents. + /// Returns the [`Scrollbar`] given the bounds and content bounds of a + /// [`Scrollable`]. /// + /// [`Scrollbar`]: struct.Scrollbar.html /// [`Scrollable`]: struct.Scrollable.html - fn is_mouse_over_scrollbar( + fn scrollbar( &self, bounds: Rectangle, content_bounds: Rectangle, - cursor_position: Point, - ) -> bool; + offset: u32, + ) -> Option; /// Draws the [`Scrollable`]. /// /// It receives: /// - the [`State`] of the [`Scrollable`] - /// - the bounds of the [`Scrollable`] + /// - the bounds of the [`Scrollable`] widget + /// - the bounds of the [`Scrollable`] content /// - whether the mouse is over the [`Scrollable`] or not - /// - whether the mouse is over the scrollbar or not + /// - whether the mouse is over the [`Scrollbar`] or not + /// - a optional [`Scrollbar`] to be rendered /// - the scrolling offset /// - the drawn content /// + /// [`Scrollbar`]: struct.Scrollbar.html /// [`Scrollable`]: struct.Scrollable.html /// [`State`]: struct.State.html fn draw( @@ -400,6 +472,7 @@ pub trait Renderer: crate::Renderer + Sized { content_bounds: Rectangle, is_mouse_over: bool, is_mouse_over_scrollbar: bool, + scrollbar: Option, offset: u32, content: Self::Output, ) -> Self::Output; diff --git a/src/settings.rs b/src/settings.rs index 2556c51b..62a1a614 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -19,6 +19,9 @@ pub struct Window { /// 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, } impl Default for Window { @@ -26,6 +29,7 @@ impl Default for Window { Window { size: (1024, 768), resizable: true, + decorations: true, } } } @@ -37,6 +41,8 @@ impl From for iced_winit::Settings { window: iced_winit::settings::Window { size: settings.window.size, resizable: settings.window.resizable, + decorations: settings.window.decorations, + platform_specific: Default::default(), }, } } diff --git a/web/CHANGELOG.md b/web/CHANGELOG.md index 49ddf5a3..0bdb9c37 100644 --- a/web/CHANGELOG.md +++ b/web/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- Render not being scheduled after `Command` futures finishing. ## [0.1.0] - 2019-11-25 ### Added diff --git a/web/src/lib.rs b/web/src/lib.rs index 8239ffc9..782bcf93 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -138,19 +138,9 @@ pub trait Application { Self: 'static + Sized, { let (app, command) = Self::new(); - let mut instance = Instance::new(app); - instance.spawn(command); - - let window = web_sys::window().unwrap(); - - let document = window.document().unwrap(); - document.set_title(&instance.title); - - let body = document.body().unwrap(); - let vdom = dodrio::Vdom::new(&body, instance); - - vdom.forget(); + let instance = Instance::new(app); + instance.run(command); } } @@ -158,6 +148,7 @@ pub trait Application { struct Instance { title: String, ui: Rc>>>, + vdom: Rc>>, } impl Instance @@ -168,6 +159,7 @@ where Self { title: ui.title(), ui: Rc::new(RefCell::new(Box::new(ui))), + vdom: Rc::new(RefCell::new(None)), } } @@ -192,11 +184,35 @@ where for future in command.futures() { let mut instance = self.clone(); - let future = future.map(move |message| instance.update(message)); + + let future = future.map(move |message| { + instance.update(message); + + if let Some(ref vdom) = *instance.vdom.borrow() { + vdom.schedule_render(); + } + }); wasm_bindgen_futures::spawn_local(future); } } + + fn run(mut self, command: Command) { + let window = web_sys::window().unwrap(); + + let document = window.document().unwrap(); + document.set_title(&self.title); + + let body = document.body().unwrap(); + + let weak = self.vdom.clone(); + self.spawn(command); + + let vdom = dodrio::Vdom::new(&body, self); + *weak.borrow_mut() = Some(vdom.weak()); + + vdom.forget(); + } } impl dodrio::Render for Instance diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f008a99c..47ec0537 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -4,14 +4,14 @@ version = "0.1.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" description = "A wgpu renderer for Iced" -license = "MIT" +license = "MIT AND OFL-1.1" repository = "https://github.com/hecrj/iced" [dependencies] iced_native = { version = "0.1.0", path = "../native" } wgpu = "0.4" glyph_brush = "0.6" -wgpu_glyph = "0.6" +wgpu_glyph = { version = "0.7", git = "https://github.com/hecrj/wgpu_glyph", branch = "fix/font-load-panic" } raw-window-handle = "0.3" image = "0.22" glam = "0.8" diff --git a/wgpu/fonts/Lato-Regular.ttf b/wgpu/fonts/Lato-Regular.ttf new file mode 100644 index 00000000..33eba8b1 Binary files /dev/null and b/wgpu/fonts/Lato-Regular.ttf differ diff --git a/wgpu/fonts/OFL.txt b/wgpu/fonts/OFL.txt new file mode 100644 index 00000000..dfca0da4 --- /dev/null +++ b/wgpu/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 4e814468..7e4e2670 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -491,6 +491,7 @@ pub struct Image { pub scale: [f32; 2], } +#[repr(C)] #[derive(Clone, Copy)] pub struct Vertex { _position: [f32; 2], @@ -513,6 +514,7 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; +#[repr(C)] #[derive(Clone, Copy)] struct Instance { _position: [f32; 2], diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 3d797758..c292dec3 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -248,6 +248,7 @@ impl Pipeline { } } +#[repr(C)] #[derive(Clone, Copy)] pub struct Vertex { _position: [f32; 2], @@ -270,6 +271,7 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; +#[repr(C)] #[derive(Debug, Clone, Copy)] pub struct Quad { pub position: [f32; 2], diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 8f0f7432..fa52bd96 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -49,7 +49,7 @@ impl<'a> Layer<'a> { impl Renderer { fn new() -> Self { let adapter = Adapter::request(&RequestAdapterOptions { - power_preference: PowerPreference::LowPower, + power_preference: PowerPreference::Default, backends: BackendBit::all(), }) .expect("Request adapter"); diff --git a/wgpu/src/renderer/widget/scrollable.rs b/wgpu/src/renderer/widget/scrollable.rs index 58dc3df9..6ef57185 100644 --- a/wgpu/src/renderer/widget/scrollable.rs +++ b/wgpu/src/renderer/widget/scrollable.rs @@ -1,45 +1,58 @@ use crate::{Primitive, Renderer}; -use iced_native::{ - scrollable, Background, MouseCursor, Point, Rectangle, Vector, -}; +use iced_native::{scrollable, Background, MouseCursor, Rectangle, Vector}; const SCROLLBAR_WIDTH: u16 = 10; const SCROLLBAR_MARGIN: u16 = 2; -fn scrollbar_bounds(bounds: Rectangle) -> Rectangle { - Rectangle { - x: bounds.x + bounds.width - - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), - y: bounds.y, - width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), - height: bounds.height, - } -} - impl scrollable::Renderer for Renderer { - fn is_mouse_over_scrollbar( + fn scrollbar( &self, bounds: Rectangle, content_bounds: Rectangle, - cursor_position: Point, - ) -> bool { - content_bounds.height > bounds.height - && scrollbar_bounds(bounds).contains(cursor_position) + offset: u32, + ) -> Option { + if content_bounds.height > bounds.height { + let scrollbar_bounds = Rectangle { + x: bounds.x + bounds.width + - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + y: bounds.y, + width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + height: bounds.height, + }; + + let ratio = bounds.height / content_bounds.height; + let scrollbar_height = bounds.height * ratio; + let y_offset = offset as f32 * ratio; + + let scroller_bounds = Rectangle { + x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), + y: scrollbar_bounds.y + y_offset, + width: scrollbar_bounds.width - f32::from(2 * SCROLLBAR_MARGIN), + height: scrollbar_height, + }; + + Some(scrollable::Scrollbar { + bounds: scrollbar_bounds, + scroller: scrollable::Scroller { + bounds: scroller_bounds, + }, + }) + } else { + None + } } fn draw( &mut self, state: &scrollable::State, bounds: Rectangle, - content_bounds: Rectangle, + _content_bounds: Rectangle, is_mouse_over: bool, is_mouse_over_scrollbar: bool, + scrollbar: Option, offset: u32, (content, mouse_cursor): Self::Output, ) -> Self::Output { - let is_content_overflowing = content_bounds.height > bounds.height; - let scrollbar_bounds = scrollbar_bounds(bounds); - let clip = Primitive::Clip { bounds, offset: Vector::new(0, offset), @@ -47,51 +60,46 @@ impl scrollable::Renderer for Renderer { }; ( - if is_content_overflowing - && (is_mouse_over || state.is_scrollbar_grabbed()) - { - let ratio = bounds.height / content_bounds.height; - let scrollbar_height = bounds.height * ratio; - let y_offset = offset as f32 * ratio; - - let scrollbar = Primitive::Quad { - bounds: Rectangle { - x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), - y: scrollbar_bounds.y + y_offset, - width: scrollbar_bounds.width - - f32::from(2 * SCROLLBAR_MARGIN), - height: scrollbar_height, - }, - background: Background::Color([0.0, 0.0, 0.0, 0.7].into()), - border_radius: 5, - }; - - if is_mouse_over_scrollbar || state.is_scrollbar_grabbed() { - let scrollbar_background = Primitive::Quad { - bounds: Rectangle { - x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), - width: scrollbar_bounds.width - - f32::from(2 * SCROLLBAR_MARGIN), - ..scrollbar_bounds - }, + if let Some(scrollbar) = scrollbar { + if is_mouse_over || state.is_scroller_grabbed() { + let scroller = Primitive::Quad { + bounds: scrollbar.scroller.bounds, background: Background::Color( - [0.0, 0.0, 0.0, 0.3].into(), + [0.0, 0.0, 0.0, 0.7].into(), ), border_radius: 5, }; - Primitive::Group { - primitives: vec![clip, scrollbar_background, scrollbar], + if is_mouse_over_scrollbar || state.is_scroller_grabbed() { + let scrollbar = Primitive::Quad { + bounds: Rectangle { + x: scrollbar.bounds.x + + f32::from(SCROLLBAR_MARGIN), + width: scrollbar.bounds.width + - f32::from(2 * SCROLLBAR_MARGIN), + ..scrollbar.bounds + }, + background: Background::Color( + [0.0, 0.0, 0.0, 0.3].into(), + ), + border_radius: 5, + }; + + Primitive::Group { + primitives: vec![clip, scrollbar, scroller], + } + } else { + Primitive::Group { + primitives: vec![clip, scroller], + } } } else { - Primitive::Group { - primitives: vec![clip, scrollbar], - } + clip } } else { clip }, - if is_mouse_over_scrollbar || state.is_scrollbar_grabbed() { + if is_mouse_over_scrollbar || state.is_scroller_grabbed() { MouseCursor::Idle } else { mouse_cursor diff --git a/wgpu/src/renderer/widget/text_input.rs b/wgpu/src/renderer/widget/text_input.rs index 9ed3b415..d64fca6d 100644 --- a/wgpu/src/renderer/widget/text_input.rs +++ b/wgpu/src/renderer/widget/text_input.rs @@ -45,7 +45,7 @@ impl text_input::Renderer for Renderer { height: bounds.height - 2.0, }, background: Background::Color(Color::WHITE), - border_radius: 5, + border_radius: 4, }; let size = f32::from(size); diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index bf37abe5..e9a1602f 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -11,6 +11,8 @@ pub const BUILTIN_ICONS: iced_native::Font = iced_native::Font::External { pub const CHECKMARK_ICON: char = '\u{F00C}'; +const FALLBACK_FONT: &[u8] = include_bytes!("../fonts/Lato-Regular.ttf"); + #[derive(Debug)] pub struct Pipeline { draw_brush: RefCell>, @@ -26,24 +28,31 @@ impl Pipeline { let default_font = font_source .load(&[font::Family::SansSerif, font::Family::Serif]) - .expect("Find sans-serif or serif font"); + .unwrap_or_else(|_| FALLBACK_FONT.to_vec()); - let mono_font = font_source - .load(&[font::Family::Monospace]) - .expect("Find monospace font"); + let load_glyph_brush = |font: Vec| { + let builder = + wgpu_glyph::GlyphBrushBuilder::using_fonts_bytes(vec![ + font.clone() + ])?; - let draw_brush = - wgpu_glyph::GlyphBrushBuilder::using_fonts_bytes(vec![ - mono_font, - default_font.clone(), - ]) + Ok(( + builder, + glyph_brush::GlyphBrushBuilder::using_font_bytes(font).build(), + )) + }; + + let (brush_builder, measure_brush) = load_glyph_brush(default_font) + .unwrap_or_else(|_: wgpu_glyph::rusttype::Error| { + log::warn!("System font failed to load. Falling back to embedded font..."); + + load_glyph_brush(FALLBACK_FONT.to_vec()).expect("Load fallback font") + }); + + let draw_brush = brush_builder .initial_cache_size((2048, 2048)) .build(device, wgpu::TextureFormat::Bgra8UnormSrgb); - let measure_brush = - glyph_brush::GlyphBrushBuilder::using_font_bytes(default_font) - .build(); - Pipeline { draw_brush: RefCell::new(draw_brush), draw_font_map: RefCell::new(HashMap::new()), @@ -95,14 +104,7 @@ impl Pipeline { text: content, scale: wgpu_glyph::Scale { x: size, y: size }, bounds: (bounds.width, bounds.height), - - // TODO: This is a bit hacky. We are loading the debug font as the - // first font in the `draw_brush`. The `measure_brush` does not - // contain this, hence we subtract 1. - // - // This should go away once we unify `draw_brush` and - // `measure_brush`. - font_id: wgpu_glyph::FontId(font_id - 1), + font_id: wgpu_glyph::FontId(font_id), ..Default::default() }; @@ -143,7 +145,7 @@ impl Pipeline { pub fn find_font(&self, font: iced_native::Font) -> wgpu_glyph::FontId { match font { - iced_native::Font::Default => wgpu_glyph::FontId(1), + iced_native::Font::Default => wgpu_glyph::FontId(0), iced_native::Font::External { name, bytes } => { if let Some(font_id) = self.draw_font_map.borrow().get(name) { return *font_id; diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 2a33255d..b5b07449 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -15,3 +15,6 @@ iced_native = { version = "0.1.0-alpha", path = "../native" } winit = { version = "0.20.0-alpha3", git = "https://github.com/rust-windowing/winit", rev = "709808eb4e69044705fcb214bcc30556db761405"} futures = { version = "0.3", features = ["thread-pool"] } log = "0.4" + +[target.'cfg(target_os = "windows")'.dependencies.winapi] +version = "0.3.6" diff --git a/winit/src/application.rs b/winit/src/application.rs index 1042b412..3772a667 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -96,21 +96,35 @@ pub trait Application: Sized { let mut title = application.title(); - let (width, height) = settings.window.size; + let window = { + let mut window_builder = WindowBuilder::new(); - let window = WindowBuilder::new() - .with_title(&title) - .with_inner_size(winit::dpi::LogicalSize { - width: f64::from(width), - height: f64::from(height), - }) - .with_resizable(settings.window.resizable) - .build(&event_loop) - .expect("Open window"); + let (width, height) = settings.window.size; + + window_builder = window_builder + .with_title(&title) + .with_inner_size(winit::dpi::LogicalSize { + width: f64::from(width), + height: f64::from(height), + }) + .with_resizable(settings.window.resizable) + .with_decorations(settings.window.decorations); + + #[cfg(target_os = "windows")] + { + use winit::platform::windows::WindowBuilderExtWindows; + + if let Some(parent) = settings.window.platform_specific.parent { + window_builder = window_builder.with_parent_window(parent); + } + } + + window_builder.build(&event_loop).expect("Open window") + }; let dpi = window.hidpi_factor(); let mut size = window.inner_size(); - let mut new_size: Option = None; + let mut resized = false; let mut renderer = Self::Renderer::new(); @@ -143,6 +157,11 @@ pub trait Application: Sized { event_loop.run(move |event, _, control_flow| match event { event::Event::MainEventsCleared => { + if events.is_empty() && external_messages.is_empty() && !resized + { + return; + } + // TODO: We should be able to keep a user interface alive // between events once we remove state references. // @@ -217,9 +236,9 @@ pub trait Application: Sized { event::Event::RedrawRequested(_) => { debug.render_started(); - if let Some(new_size) = new_size.take() { + if resized { let dpi = window.hidpi_factor(); - let (width, height) = to_physical(new_size, dpi); + let (width, height) = to_physical(size, dpi); target.resize( width, @@ -228,7 +247,7 @@ pub trait Application: Sized { &renderer, ); - size = new_size; + resized = false; } let new_mouse_cursor = @@ -320,8 +339,9 @@ pub trait Application: Sized { WindowEvent::CloseRequested => { *control_flow = ControlFlow::Exit; } - WindowEvent::Resized(size) => { - new_size = Some(size.into()); + WindowEvent::Resized(new_size) => { + size = new_size; + resized = true; log::debug!("Resized: {:?}", new_size); } diff --git a/winit/src/debug/basic.rs b/winit/src/debug/basic.rs index 67c6d8a2..c9da392c 100644 --- a/winit/src/debug/basic.rs +++ b/winit/src/debug/basic.rs @@ -146,7 +146,7 @@ impl Debug { let mut lines = Vec::new(); fn key_value(key: &str, value: T) -> String { - format!("{: <30} {:?}", key, value) + format!("{} {:?}", key, value) } lines.push(format!( diff --git a/winit/src/settings.rs b/winit/src/settings/mod.rs similarity index 59% rename from winit/src/settings.rs rename to winit/src/settings/mod.rs index d257ecd8..151d73d7 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings/mod.rs @@ -1,5 +1,11 @@ //! Configure your application. +#[cfg_attr(target_os = "windows", path = "windows.rs")] +#[cfg_attr(not(target_os = "windows"), path = "not_windows.rs")] +mod platform; + +pub use platform::PlatformSpecific; + /// The settings of an application. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Settings { @@ -17,6 +23,12 @@ pub struct Window { /// 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, + + /// Platform specific settings. + pub platform_specific: platform::PlatformSpecific, } impl Default for Window { @@ -24,6 +36,8 @@ impl Default for Window { Window { size: (1024, 768), resizable: true, + decorations: true, + platform_specific: Default::default(), } } } diff --git a/winit/src/settings/not_windows.rs b/winit/src/settings/not_windows.rs new file mode 100644 index 00000000..5d703f84 --- /dev/null +++ b/winit/src/settings/not_windows.rs @@ -0,0 +1,6 @@ +#![cfg(not(target_os = "windows"))] +//! Platform specific settings for not Windows. + +/// The platform specific window settings of an application. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct PlatformSpecific {} diff --git a/winit/src/settings/windows.rs b/winit/src/settings/windows.rs new file mode 100644 index 00000000..76b8d067 --- /dev/null +++ b/winit/src/settings/windows.rs @@ -0,0 +1,9 @@ +#![cfg(target_os = "windows")] +//! Platform specific settings for Windows. + +/// The platform specific window settings of an application. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct PlatformSpecific { + /// Parent Window + pub parent: Option, +}