diff --git a/examples/editor-libcosmic/Cargo.toml b/examples/editor-libcosmic/Cargo.toml index f16b064..db6e730 100644 --- a/examples/editor-libcosmic/Cargo.toml +++ b/examples/editor-libcosmic/Cargo.toml @@ -15,7 +15,7 @@ log = "0.4" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic" -rev = "24709e9c3b56c49a0af168d1e37ebef85f112c8a" +branch = "scale-factor" default-features = false features = ["winit_softbuffer"] #path = "../../../libcosmic" diff --git a/examples/editor-libcosmic/src/main.rs b/examples/editor-libcosmic/src/main.rs index 6635067..4a8e862 100644 --- a/examples/editor-libcosmic/src/main.rs +++ b/examples/editor-libcosmic/src/main.rs @@ -8,7 +8,7 @@ use cosmic::{ }, settings, theme::{self, Theme, ThemeType}, - widget::{button, toggler}, + widget::{button, text, toggler}, Element, }; use cosmic_text::{ @@ -16,9 +16,6 @@ use cosmic_text::{ }; use std::{env, fmt, fs, path::PathBuf, sync::Mutex}; -use self::text::text; -mod text; - use self::text_box::text_box; mod text_box; diff --git a/examples/editor-libcosmic/src/text.rs b/examples/editor-libcosmic/src/text.rs deleted file mode 100644 index dfc8d13..0000000 --- a/examples/editor-libcosmic/src/text.rs +++ /dev/null @@ -1,285 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use cosmic::{ - iced_native::{ - image, - layout::{self, Layout}, - renderer, - widget::{self, tree, Widget}, - {Color, Element, Length, Point, Rectangle, Size}, - }, - iced_winit::renderer::BorderRadius, - theme::{Theme, ThemeType}, -}; -use cosmic_text::{Attrs, AttrsList, BufferLine, Metrics, SwashCache}; -use std::{cmp, sync::Mutex, time::Instant}; - -use crate::FONT_SYSTEM; - -pub struct Appearance { - background_color: Option, - text_color: Color, -} - -pub trait StyleSheet { - fn appearance(&self) -> Appearance; -} - -impl StyleSheet for Theme { - fn appearance(&self) -> Appearance { - match self.theme_type { - ThemeType::Dark | ThemeType::HighContrastDark => Appearance { - background_color: None, - text_color: Color::from_rgb8(0xFF, 0xFF, 0xFF), - }, - ThemeType::Light | ThemeType::HighContrastLight => Appearance { - background_color: None, - text_color: Color::from_rgb8(0x00, 0x00, 0x00), - }, - } - } -} - -pub struct Text { - line: BufferLine, - metrics: Metrics, -} - -impl Text { - pub fn new(string: &str) -> Self { - let instant = Instant::now(); - - //TODO: make it possible to set attrs - let mut line = BufferLine::new(string, AttrsList::new(Attrs::new())); - - //TODO: do we have to immediately shape? - line.shape(&mut FONT_SYSTEM.lock().unwrap()); - - let text = Self { - line, - metrics: Metrics::new(14.0, 20.0), - }; - - log::debug!("Text::new in {:?}", instant.elapsed()); - - text - } -} - -pub fn text(string: &str) -> Text { - Text::new(string) -} - -impl Widget for Text -where - Renderer: renderer::Renderer + image::Renderer, - Renderer::Theme: StyleSheet, -{ - fn tag(&self) -> tree::Tag { - tree::Tag::of::() - } - - fn state(&self) -> tree::State { - tree::State::new(State::new()) - } - - fn width(&self) -> Length { - Length::Shrink - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node { - let instant = Instant::now(); - - let limits = limits.width(Length::Shrink).height(Length::Shrink); - - let shape = self.line.shape_opt().as_ref().unwrap(); - - //TODO: can we cache this? - let layout_lines = shape.layout( - self.metrics.font_size, - limits.max().width, - self.line.wrap(), - self.line.align(), - ); - - let mut width = 0.0f32; - let mut height = 0.0f32; - - for layout_line in layout_lines { - for glyph in layout_line.glyphs.iter() { - width = width.max((glyph.x + glyph.w) + 1.0); - } - height += self.metrics.line_height; - } - - let size = Size::new(width, height); - - log::debug!("layout {:?} in {:?}", size, instant.elapsed()); - - layout::Node::new(limits.resolve(size)) - } - - fn draw( - &self, - tree: &widget::Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - ) { - let instant = Instant::now(); - - let state = tree.state.downcast_ref::(); - - let appearance = theme.appearance(); - - if let Some(background_color) = appearance.background_color { - renderer.fill_quad( - renderer::Quad { - bounds: layout.bounds(), - border_radius: BorderRadius::default(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - background_color, - ); - } - - let text_color = cosmic_text::Color::rgba( - cmp::max(0, cmp::min(255, (appearance.text_color.r * 255.0) as i32)) as u8, - cmp::max(0, cmp::min(255, (appearance.text_color.g * 255.0) as i32)) as u8, - cmp::max(0, cmp::min(255, (appearance.text_color.b * 255.0) as i32)) as u8, - cmp::max(0, cmp::min(255, (appearance.text_color.a * 255.0) as i32)) as u8, - ); - - let layout_w = layout.bounds().width; - let layout_h = layout.bounds().height; - - let shape = self.line.shape_opt().as_ref().unwrap(); - - //TODO: can we cache this? - let layout_lines = shape.layout( - self.metrics.font_size, - layout_w, - self.line.wrap(), - self.line.align(), - ); - - let mut cache = state.cache.lock().unwrap(); - - let mut pixels = vec![0; layout_w as usize * layout_h as usize * 4]; - - let mut line_y = self.metrics.font_size; - for layout_line in layout_lines { - for glyph in layout_line.glyphs.iter() { - let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int); - - let glyph_color = match glyph.color_opt { - Some(some) => some, - None => text_color, - }; - - cache.with_pixels( - &mut FONT_SYSTEM.lock().unwrap(), - cache_key, - glyph_color, - |pixel_x, pixel_y, color| { - let x = x_int + pixel_x; - let y = line_y as i32 + y_int + pixel_y; - draw_pixel(&mut pixels, layout_w as i32, layout_h as i32, x, y, color); - }, - ); - } - line_y += self.metrics.line_height; - } - - let handle = image::Handle::from_pixels(layout_w as u32, layout_h as u32, pixels); - image::Renderer::draw(renderer, handle, layout.bounds()); - - log::trace!("draw {:?} in {:?}", layout.bounds(), instant.elapsed()); - } -} - -pub fn draw_pixel( - buffer: &mut [u8], - width: i32, - height: i32, - x: i32, - y: i32, - color: cosmic_text::Color, -) { - let alpha = (color.0 >> 24) & 0xFF; - if alpha == 0 { - // Do not draw if alpha is zero - return; - } - - if y < 0 || y >= height { - // Skip if y out of bounds - return; - } - - if x < 0 || x >= width { - // Skip if x out of bounds - return; - } - - let offset = (y as usize * width as usize + x as usize) * 4; - - let mut current = buffer[offset + 2] as u32 - | (buffer[offset + 1] as u32) << 8 - | (buffer[offset] as u32) << 16 - | (buffer[offset + 3] as u32) << 24; - - if alpha >= 255 || current == 0 { - // Alpha is 100% or current is null, replace with no blending - current = color.0; - } else { - // Alpha blend with current value - let n_alpha = 255 - alpha; - let rb = ((n_alpha * (current & 0x00FF00FF)) + (alpha * (color.0 & 0x00FF00FF))) >> 8; - let ag = (n_alpha * ((current & 0xFF00FF00) >> 8)) - + (alpha * (0x01000000 | ((color.0 & 0x0000FF00) >> 8))); - current = (rb & 0x00FF00FF) | (ag & 0xFF00FF00); - } - - buffer[offset + 2] = current as u8; - buffer[offset + 1] = (current >> 8) as u8; - buffer[offset] = (current >> 16) as u8; - buffer[offset + 3] = (current >> 24) as u8; -} - -impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> -where - Renderer: renderer::Renderer + image::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from(text: Text) -> Self { - Self::new(text) - } -} - -pub struct State { - cache: Mutex, -} - -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - let instant = Instant::now(); - - let state = State { - cache: Mutex::new(SwashCache::new()), - }; - - log::debug!("created state in {:?}", instant.elapsed()); - - state - } -} diff --git a/examples/editor-libcosmic/src/text_box.rs b/examples/editor-libcosmic/src/text_box.rs index e1dfd6f..91c8d69 100644 --- a/examples/editor-libcosmic/src/text_box.rs +++ b/examples/editor-libcosmic/src/text_box.rs @@ -1,8 +1,5 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::FONT_SYSTEM; - -use super::text; use cosmic::{ iced_native::{ clipboard::Clipboard, @@ -15,12 +12,13 @@ use cosmic::{ widget::{self, tree, Widget}, Padding, {Color, Element, Length, Point, Rectangle, Shell, Size}, }, - iced_winit::renderer::BorderRadius, theme::{Theme, ThemeType}, }; use cosmic_text::{Action, Edit, SwashCache}; use std::{cmp, sync::Mutex, time::Instant}; +use crate::FONT_SYSTEM; + pub struct Appearance { background_color: Option, text_color: Color, @@ -64,11 +62,60 @@ impl<'a, Editor> TextBox<'a, Editor> { } } -pub fn text_box(editor: &Mutex) -> TextBox { +pub fn text_box<'a, Editor>(editor: &'a Mutex) -> TextBox<'a, Editor> { TextBox::new(editor) } -impl<'a, Editor, Message, Renderer> Widget for TextBox<'a, Editor> +fn draw_pixel( + buffer: &mut [u8], + width: i32, + height: i32, + x: i32, + y: i32, + color: cosmic_text::Color, +) { + let alpha = (color.0 >> 24) & 0xFF; + if alpha == 0 { + // Do not draw if alpha is zero + return; + } + + if y < 0 || y >= height { + // Skip if y out of bounds + return; + } + + if x < 0 || x >= width { + // Skip if x out of bounds + return; + } + + let offset = (y as usize * width as usize + x as usize) * 4; + + let mut current = buffer[offset + 2] as u32 + | (buffer[offset + 1] as u32) << 8 + | (buffer[offset + 0] as u32) << 16 + | (buffer[offset + 3] as u32) << 24; + + if alpha >= 255 || current == 0 { + // Alpha is 100% or current is null, replace with no blending + current = color.0; + } else { + // Alpha blend with current value + let n_alpha = 255 - alpha; + let rb = ((n_alpha * (current & 0x00FF00FF)) + (alpha * (color.0 & 0x00FF00FF))) >> 8; + let ag = (n_alpha * ((current & 0xFF00FF00) >> 8)) + + (alpha * (0x01000000 | ((color.0 & 0x0000FF00) >> 8))); + current = (rb & 0x00FF00FF) | (ag & 0xFF00FF00); + } + + buffer[offset + 2] = current as u8; + buffer[offset + 1] = (current >> 8) as u8; + buffer[offset + 0] = (current >> 16) as u8; + buffer[offset + 3] = (current >> 24) as u8; +} + +impl<'a, 'editor, Editor, Message, Renderer> Widget for TextBox<'a, Editor> where Renderer: renderer::Renderer + image::Renderer, Renderer::Theme: StyleSheet, @@ -135,11 +182,13 @@ where tree: &widget::Tree, renderer: &mut Renderer, theme: &Renderer::Theme, - _style: &renderer::Style, + style: &renderer::Style, layout: Layout<'_>, _cursor_position: Point, viewport: &Rectangle, ) { + let instant = Instant::now(); + let state = tree.state.downcast_ref::(); let appearance = theme.appearance(); @@ -148,7 +197,7 @@ where renderer.fill_quad( renderer::Quad { bounds: layout.bounds(), - border_radius: BorderRadius::default(), + border_radius: 0.0.into(), border_width: 0.0, border_color: Color::TRANSPARENT, }, @@ -165,63 +214,52 @@ where let mut editor = self.editor.lock().unwrap(); - let view_w = viewport.width.min(layout.bounds().width) - self.padding.horizontal() as f32; - let view_h = viewport.height.min(layout.bounds().height) - self.padding.vertical() as f32; + let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32) + - self.padding.horizontal() as i32; + let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32) + - self.padding.vertical() as i32; + + let image_w = (view_w as f64 * style.scale_factor) as i32; + let image_h = (view_h as f64 * style.scale_factor) as i32; let mut font_system = FONT_SYSTEM.lock().unwrap(); let mut editor = editor.borrow_with(&mut font_system); - editor.buffer_mut().set_size(view_w, view_h); + // Scale metrics + let metrics = editor.buffer().metrics(); + editor.buffer_mut().set_metrics(metrics.scale(style.scale_factor as f32)); + // Set size + editor.buffer_mut().set_size(image_w as f32, image_h as f32); + + // Shape and layout editor.shape_as_needed(); - let instant = Instant::now(); - - let mut pixels = vec![0; view_w as usize * view_h as usize * 4]; - + // Draw to pixel buffer + let mut pixels = vec![0; image_w as usize * image_h as usize * 4]; editor.draw( &mut state.cache.lock().unwrap(), text_color, |x, y, w, h, color| { - if w == 0 || h == 0 { - // Do not draw invalid sized rectangles - return; - } - - if w > 1 || h > 1 { - // Draw rectangles with optimized quad renderer - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle::new( - layout.position() - + [x as f32, y as f32].into() - + [self.padding.left as f32, self.padding.top as f32].into(), - Size::new(w as f32, h as f32), - ), - border_radius: BorderRadius::default(), - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - Color::from_rgba8( - color.r(), - color.g(), - color.b(), - (color.a() as f32) / 255.0, - ), - ); - } else { - text::draw_pixel(&mut pixels, view_w as i32, view_h as i32, x, y, color); + //TODO: improve performance + for row in 0..h as i32 { + for col in 0..w as i32 { + draw_pixel(&mut pixels, image_w, image_h, x + col, y + row, color); + } } }, ); - let handle = image::Handle::from_pixels(view_w as u32, view_h as u32, pixels); + // Restore original metrics + editor.buffer_mut().set_metrics(metrics); + + let handle = image::Handle::from_pixels(image_w as u32, image_h as u32, pixels); image::Renderer::draw( renderer, handle, Rectangle::new( layout.position() + [self.padding.left as f32, self.padding.top as f32].into(), - Size::new(view_w, view_h), + Size::new(view_w as f32, view_h as f32), ), ); @@ -246,7 +284,10 @@ where let mut status = Status::Ignored; match event { - Event::Keyboard(KeyEvent::KeyPressed { key_code, .. }) => match key_code { + Event::Keyboard(KeyEvent::KeyPressed { + key_code, + modifiers, + }) => match key_code { KeyCode::Left => { editor.action(Action::Left); status = Status::Captured; @@ -326,14 +367,15 @@ where status = Status::Captured; } } - Event::Mouse(MouseEvent::WheelScrolled { - delta: ScrollDelta::Lines { y, .. }, - }) => { - editor.action(Action::Scroll { - lines: (-y * 6.0) as i32, - }); - status = Status::Captured; - } + Event::Mouse(MouseEvent::WheelScrolled { delta }) => match delta { + ScrollDelta::Lines { x, y } => { + editor.action(Action::Scroll { + lines: (-y * 6.0) as i32, + }); + status = Status::Captured; + } + _ => (), + }, _ => (), } @@ -341,7 +383,8 @@ where } } -impl<'a, Editor, Message, Renderer> From> for Element<'a, Message, Renderer> +impl<'a, 'editor, Editor, Message, Renderer> From> + for Element<'a, Message, Renderer> where Renderer: renderer::Renderer + image::Renderer, Renderer::Theme: StyleSheet,