diff --git a/editor-libcosmic-image.sh b/editor-libcosmic-image.sh deleted file mode 100755 index 444e2cb..0000000 --- a/editor-libcosmic-image.sh +++ /dev/null @@ -1,3 +0,0 @@ -# SPDX-License-Identifier: MIT OR Apache-2.0 - -RUST_LOG="cosmic_text=debug,editor_libcosmic_image=debug" cargo run --release --package editor-libcosmic-image -- "$@" diff --git a/examples/editor-libcosmic-image/Cargo.toml b/examples/editor-libcosmic-image/Cargo.toml deleted file mode 100644 index 58cbb0e..0000000 --- a/examples/editor-libcosmic-image/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "editor-libcosmic-image" -version = "0.1.0" -authors = ["Jeremy Soller "] -edition = "2021" -license = "MIT OR Apache-2.0" -publish = false - -[dependencies] -cosmic-text = { path = "../../" } -env_logger = "0.9" -fontdb = "0.9" -lazy_static = "1.4" -log = "0.4" - -[dependencies.libcosmic] -git = "https://github.com/pop-os/libcosmic" -branch = "cosmic-design-system" -#path = "../../../libcosmic" - -[dependencies.rfd] -version = "0.10" -#TODO: iced portal -#default-features = false -#features = ["xdg-portal"] diff --git a/examples/editor-libcosmic-image/src/main.rs b/examples/editor-libcosmic-image/src/main.rs deleted file mode 100644 index 5e61aad..0000000 --- a/examples/editor-libcosmic-image/src/main.rs +++ /dev/null @@ -1,268 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use cosmic::{ - iced::{ - self, - Alignment, - Application, - Command, - Element, - Length, - Theme, - widget::{ - column, - horizontal_space, - pick_list, - row, - text, - }, - }, - settings, - widget::{ - button, - toggler, - }, -}; -use cosmic_text::{ - Attrs, - AttrsList, - FontSystem, - SwashCache, - TextBuffer, - TextMetrics, -}; -use std::{ - env, - fs, - path::PathBuf, - sync::Mutex, -}; - -use self::text_box::text_box; -mod text_box; - -lazy_static::lazy_static! { - static ref FONT_SYSTEM: FontSystem<'static> = FontSystem::new(); -} - -static FONT_SIZES: &'static [TextMetrics] = &[ - TextMetrics::new(10, 14), // Caption - TextMetrics::new(14, 20), // Body - TextMetrics::new(20, 28), // Title 4 - TextMetrics::new(24, 32), // Title 3 - TextMetrics::new(28, 36), // Title 2 - TextMetrics::new(32, 44), // Title 1 -]; - -fn main() -> cosmic::iced::Result { - env_logger::init(); - - let mut settings = settings(); - settings.window.min_size = Some((400, 100)); - Window::run(settings) -} - -pub struct Window { - theme: Theme, - path_opt: Option, - attrs: Attrs<'static>, - buffer: Mutex>, - cache: Mutex>, -} - -#[allow(dead_code)] -#[derive(Clone, Copy, Debug)] -pub enum Message { - Open, - Save, - Bold(bool), - Italic(bool), - Monospaced(bool), - MetricsChanged(TextMetrics), - ThemeChanged(&'static str), -} - -impl Window { - pub fn open(&mut self, path: PathBuf) { - let mut buffer = self.buffer.lock().unwrap(); - match fs::read_to_string(&path) { - Ok(text) => { - log::info!("opened '{}'", path.display()); - buffer.set_text(&text, self.attrs); - self.path_opt = Some(path); - }, - Err(err) => { - log::error!("failed to open '{}': {}", path.display(), err); - buffer.set_text("", self.attrs); - self.path_opt = None; - } - } - } -} - -impl Application for Window { - type Executor = iced::executor::Default; - type Flags = (); - type Message = Message; - type Theme = Theme; - - fn new(_flags: ()) -> (Self, Command) { - let attrs = cosmic_text::Attrs::new() - .monospaced(true) - .family(cosmic_text::Family::Monospace); - - let buffer = TextBuffer::new( - &FONT_SYSTEM, - FONT_SIZES[1 /* Body */], - ); - - let cache = SwashCache::new(&FONT_SYSTEM); - - let mut window = Window { - theme: Theme::Dark, - path_opt: None, - attrs, - buffer: Mutex::new(buffer), - cache: Mutex::new(cache), - }; - if let Some(arg) = env::args().nth(1) { - window.open(PathBuf::from(arg)); - } - (window, Command::none()) - } - - fn theme(&self) -> Theme { - self.theme - } - - fn title(&self) -> String { - if let Some(path) = &self.path_opt { - format!("COSMIC Text - {} - {}", FONT_SYSTEM.locale, path.display()) - } else { - format!("COSMIC Text - {}", FONT_SYSTEM.locale) - } - } - - fn update(&mut self, message: Message) -> iced::Command { - match message { - Message::Open => { - if let Some(path) = rfd::FileDialog::new().pick_file() { - self.open(path); - } - }, - Message::Save => { - if let Some(path) = &self.path_opt { - let buffer = self.buffer.lock().unwrap(); - let mut text = String::new(); - for line in buffer.lines.iter() { - text.push_str(line.text()); - text.push('\n'); - } - match fs::write(path, text) { - Ok(()) => { - log::info!("saved '{}'", path.display()); - }, - Err(err) => { - log::error!("failed to save '{}': {}", path.display(), err); - } - } - } - }, - Message::Bold(bold) => { - self.attrs = self.attrs.weight(if bold { - cosmic_text::Weight::BOLD - } else { - cosmic_text::Weight::NORMAL - }); - - let mut buffer = self.buffer.lock().unwrap(); - for line in buffer.lines.iter_mut() { - line.set_attrs_list(AttrsList::new(self.attrs)); - } - }, - Message::Italic(italic) => { - self.attrs = self.attrs.style(if italic { - cosmic_text::Style::Italic - } else { - cosmic_text::Style::Normal - }); - - let mut buffer = self.buffer.lock().unwrap(); - for line in buffer.lines.iter_mut() { - line.set_attrs_list(AttrsList::new(self.attrs)); - } - }, - Message::Monospaced(monospaced) => { - self.attrs = self.attrs - .family(if monospaced { - cosmic_text::Family::Monospace - } else { - cosmic_text::Family::SansSerif - }) - .monospaced(monospaced); - - let mut buffer = self.buffer.lock().unwrap(); - for line in buffer.lines.iter_mut() { - line.set_attrs_list(AttrsList::new(self.attrs)); - } - }, - Message::MetricsChanged(metrics) => { - let mut buffer = self.buffer.lock().unwrap(); - buffer.set_metrics(metrics); - }, - Message::ThemeChanged(theme) => match theme { - "Dark" => self.theme = Theme::Dark, - "Light" => self.theme = Theme::Light, - _ => (), - }, - } - - Command::none() - } - - fn view(&self) -> Element { - static THEMES: &'static [&'static str] = &["Dark", "Light"]; - let theme_picker = pick_list( - THEMES, - Some(match self.theme { - Theme::Dark => THEMES[0], - Theme::Light => THEMES[1], - }), - Message::ThemeChanged - ); - - let font_size_picker = { - let buffer = self.buffer.lock().unwrap(); - pick_list( - FONT_SIZES, - Some(buffer.metrics()), - Message::MetricsChanged - ) - }; - - column![ - row![ - button!("Open").on_press(Message::Open), - button!("Save").on_press(Message::Save), - horizontal_space(Length::Fill), - text("Bold:"), - toggler(None, self.attrs.weight == cosmic_text::Weight::BOLD, Message::Bold), - text("Italic:"), - toggler(None, self.attrs.style == cosmic_text::Style::Italic, Message::Italic), - text("Monospaced:"), - toggler(None, self.attrs.monospaced, Message::Monospaced), - text("Theme:"), - theme_picker, - text("Font Size:"), - font_size_picker, - ] - .align_items(Alignment::Center) - .spacing(8) - , - text_box(&self.buffer, &self.cache) - ] - .spacing(8) - .padding(16) - .into() - } -} diff --git a/examples/editor-libcosmic-image/src/text_box.rs b/examples/editor-libcosmic-image/src/text_box.rs deleted file mode 100644 index fb6be90..0000000 --- a/examples/editor-libcosmic-image/src/text_box.rs +++ /dev/null @@ -1,344 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 - -use cosmic::iced_native::{ - {Color, Element, Length, Point, Rectangle, Shell, Theme}, - clipboard::Clipboard, - event::{ - Event, - Status, - }, - image, - keyboard::{Event as KeyEvent, KeyCode}, - layout::{self, Layout}, - mouse::{self, Button, Event as MouseEvent, ScrollDelta}, - renderer, - widget::{self, tree, Widget}, -}; -use cosmic_text::{ - SwashCache, - TextAction, - TextBuffer, -}; -use std::{ - sync::Mutex, - time::Instant, -}; - -pub struct Appearance { - background_color: Option, -} - -pub trait StyleSheet { - fn appearance(&self) -> Appearance; -} - -impl StyleSheet for Theme { - fn appearance(&self) -> Appearance { - match self { - Theme::Dark => Appearance { - background_color: Some(Color::from_rgb8(0x34, 0x34, 0x34)), - }, - Theme::Light => Appearance { - background_color: Some(Color::from_rgb8(0xFC, 0xFC, 0xFC)), - }, - } - } -} - -pub struct TextBox<'a> { - buffer: &'a Mutex>, - cache: &'a Mutex>, - pixels_opt: Option<(u32, u32, Vec)>, -} - -impl<'a> TextBox<'a> { - pub fn new(buffer: &'a Mutex>, cache: &'a Mutex>) -> Self { - Self { - buffer, - cache, - pixels_opt: None, - } - } -} - -pub fn text_box<'a>(buffer: &'a Mutex>, cache: &'a Mutex>) -> TextBox<'a> { - TextBox::new(buffer, cache) -} - -impl<'a, Message, Renderer> Widget for TextBox<'a> -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::Fill - } - - fn height(&self) -> Length { - Length::Fill - } - - fn layout( - &self, - _renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - layout::Node::new(limits.max()) - } - - fn mouse_interaction( - &self, - _tree: &widget::Tree, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - if layout.bounds().contains(cursor_position) { - mouse::Interaction::Text - } else { - mouse::Interaction::Idle - } - } - - fn draw( - &self, - _tree: &widget::Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - _style: &renderer::Style, - layout: Layout<'_>, - _cursor_position: Point, - _viewport: &Rectangle, - ) { - if let Some(background_color) = theme.appearance().background_color { - renderer.fill_quad( - renderer::Quad { - bounds: layout.bounds(), - border_radius: 0.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - background_color - ); - } - - if let Some((w, h, pixels)) = &self.pixels_opt { - let handle = image::Handle::from_pixels(*w, *h, pixels.clone()); - image::Renderer::draw(renderer, handle, layout.bounds()); - } - } - - fn on_event( - &mut self, - tree: &mut widget::Tree, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - _renderer: &Renderer, - _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, - ) -> Status { - let state = tree.state.downcast_mut::(); - let mut buffer = self.buffer.lock().unwrap(); - - let layout_w = layout.bounds().width as i32; - let layout_h = layout.bounds().height as i32; - buffer.set_size(layout_w, layout_h); - - let mut status = Status::Ignored; - match event { - Event::Keyboard(KeyEvent::KeyPressed { key_code, modifiers }) => match key_code { - KeyCode::Left => { - buffer.action(TextAction::Left); - status = Status::Captured; - }, - KeyCode::Right => { - buffer.action(TextAction::Right); - status = Status::Captured; - }, - KeyCode::Up => { - buffer.action(TextAction::Up); - status = Status::Captured; - }, - KeyCode::Down => { - buffer.action(TextAction::Down); - status = Status::Captured; - }, - KeyCode::Home => { - buffer.action(TextAction::Home); - status = Status::Captured; - }, - KeyCode::End => { - buffer.action(TextAction::End); - status = Status::Captured; - }, - KeyCode::PageUp => { - buffer.action(TextAction::PageUp); - status = Status::Captured; - }, - KeyCode::PageDown => { - buffer.action(TextAction::PageDown); - status = Status::Captured; - }, - KeyCode::Enter => { - buffer.action(TextAction::Enter); - status = Status::Captured; - }, - KeyCode::Backspace => { - buffer.action(TextAction::Backspace); - status = Status::Captured; - }, - KeyCode::Delete => { - buffer.action(TextAction::Delete); - status = Status::Captured; - }, - _ => () - }, - Event::Keyboard(KeyEvent::CharacterReceived(character)) => { - buffer.action(TextAction::Insert(character)); - status = Status::Captured; - }, - Event::Mouse(MouseEvent::ButtonPressed(Button::Left)) => { - if layout.bounds().contains(cursor_position) { - buffer.action(TextAction::Click { - x: (cursor_position.x - layout.bounds().x) as i32, - y: (cursor_position.y - layout.bounds().y) as i32, - }); - state.is_dragging = true; - status = Status::Captured; - } - }, - Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => { - state.is_dragging = false; - status = Status::Captured; - }, - Event::Mouse(MouseEvent::CursorMoved { .. }) => { - if state.is_dragging { - buffer.action(TextAction::Drag { - x: (cursor_position.x - layout.bounds().x) as i32, - y: (cursor_position.y - layout.bounds().y) as i32, - }); - status = Status::Captured; - } - }, - Event::Mouse(MouseEvent::WheelScrolled { delta }) => match delta { - ScrollDelta::Lines { x, y } => { - buffer.action(TextAction::Scroll { - lines: (-y * 6.0) as i32, - }); - status = Status::Captured; - }, - _ => (), - }, - _ => () - } - - if buffer.cursor_moved { - buffer.shape_until_cursor(); - buffer.cursor_moved = false; - } else { - buffer.shape_until_scroll(); - } - - if layout_w < 0 || layout_h < 0 { - // Invalid size, clear pixels - self.pixels_opt = None; - } else if buffer.redraw { - // Redraw buffer to image - - let instant = Instant::now(); - - let mut pixels = vec![0; layout_w as usize * layout_h as usize * 4]; - - //TODO: load from theme somehow - let text_color = cosmic_text::Color::rgb(0xFF, 0xFF, 0xFF); - buffer.draw(&mut self.cache.lock().unwrap(), text_color, |start_x, start_y, w, h, color| { - let alpha = (color.0 >> 24) & 0xFF; - if alpha == 0 { - // Do not draw if alpha is zero - return; - } - - for y in start_y..start_y + h as i32{ - if y < 0 || y >= layout_h { - // Skip if y out of bounds - continue; - } - - let offset_y = y as usize * layout_w as usize * 4; - for x in start_x..start_x + w as i32 { - if x < 0 || x >= layout_w { - // Skip if x out of bounds - continue; - } - - let offset = offset_y + x as usize * 4; - - let mut current = - pixels[offset + 2] as u32 | - (pixels[offset + 1] as u32) << 8 | - (pixels[offset] as u32) << 16 | - (pixels[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); - } - - pixels[offset + 2] = current as u8; - pixels[offset + 1] = (current >> 8) as u8; - pixels[offset] = (current >> 16) as u8; - pixels[offset + 3] = (current >> 24) as u8; - } - } - }); - - self.pixels_opt = Some((layout_w as u32, layout_h as u32, pixels)); - - buffer.redraw = false; - - let duration = instant.elapsed(); - log::debug!("redraw: {:?}", duration); - } - - status - } -} - -impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> -where - Renderer: renderer::Renderer + image::Renderer, - Renderer::Theme: StyleSheet, -{ - fn from(text_box: TextBox<'a>) -> Self { - Self::new(text_box) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { - is_dragging: bool, -} - -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - State::default() - } -} diff --git a/examples/editor-libcosmic/src/text_box.rs b/examples/editor-libcosmic/src/text_box.rs index 25382ca..fb6be90 100644 --- a/examples/editor-libcosmic/src/text_box.rs +++ b/examples/editor-libcosmic/src/text_box.rs @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use cosmic::iced_native::{ - {Color, Element, Length, Point, Rectangle, Size, Shell, Theme}, + {Color, Element, Length, Point, Rectangle, Shell, Theme}, clipboard::Clipboard, event::{ Event, Status, }, + image, keyboard::{Event as KeyEvent, KeyCode}, layout::{self, Layout}, mouse::{self, Button, Event as MouseEvent, ScrollDelta}, @@ -19,14 +20,12 @@ use cosmic_text::{ TextBuffer, }; use std::{ - cmp, sync::Mutex, time::Instant, }; pub struct Appearance { background_color: Option, - text_color: Color, } pub trait StyleSheet { @@ -38,11 +37,9 @@ impl StyleSheet for Theme { match self { Theme::Dark => Appearance { background_color: Some(Color::from_rgb8(0x34, 0x34, 0x34)), - text_color: Color::from_rgb8(0xFF, 0xFF, 0xFF), }, Theme::Light => Appearance { background_color: Some(Color::from_rgb8(0xFC, 0xFC, 0xFC)), - text_color: Color::from_rgb8(0x00, 0x00, 0x00), }, } } @@ -51,11 +48,16 @@ impl StyleSheet for Theme { pub struct TextBox<'a> { buffer: &'a Mutex>, cache: &'a Mutex>, + pixels_opt: Option<(u32, u32, Vec)>, } impl<'a> TextBox<'a> { pub fn new(buffer: &'a Mutex>, cache: &'a Mutex>) -> Self { - Self { buffer, cache } + Self { + buffer, + cache, + pixels_opt: None, + } } } @@ -65,7 +67,7 @@ pub fn text_box<'a>(buffer: &'a Mutex>, cache: &'a Mutex Widget for TextBox<'a> where - Renderer: renderer::Renderer, + Renderer: renderer::Renderer + image::Renderer, Renderer::Theme: StyleSheet, { fn tag(&self) -> tree::Tag { @@ -89,14 +91,7 @@ where _renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - println!("{:?}", limits); - let size = limits.max(); - { - let mut buffer = self.buffer.lock().unwrap(); - - buffer.set_size(size.width as i32, size.height as i32); - } - layout::Node::new(size) + layout::Node::new(limits.max()) } fn mouse_interaction( @@ -116,7 +111,7 @@ where fn draw( &self, - _state: &widget::Tree, + _tree: &widget::Tree, renderer: &mut Renderer, theme: &Renderer::Theme, _style: &renderer::Style, @@ -124,17 +119,7 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - let appearance = theme.appearance(); - 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 instant = Instant::now(); - - if let Some(background_color) = appearance.background_color { + if let Some(background_color) = theme.appearance().background_color { renderer.fill_quad( renderer::Quad { bounds: layout.bounds(), @@ -146,41 +131,10 @@ where ); } - let mut buffer = self.buffer.lock().unwrap(); - let mut cache = self.cache.lock().unwrap(); - - if buffer.cursor_moved { - buffer.shape_until_cursor(); - buffer.cursor_moved = false; - } else { - buffer.shape_until_scroll(); + if let Some((w, h, pixels)) = &self.pixels_opt { + let handle = image::Handle::from_pixels(*w, *h, pixels.clone()); + image::Renderer::draw(renderer, handle, layout.bounds()); } - - let buffer_x = layout.bounds().x; - let buffer_y = layout.bounds().y; - buffer.draw(&mut cache, text_color, |x, y, w, h, color| { - let a = color.a(); - if a > 0 { - let r = color.r(); - let g = color.g(); - let b = color.b(); - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle::new( - Point::new(buffer_x + x as f32, buffer_y + y as f32), - Size::new(w as f32, h as f32) - ), - border_radius: 0.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - }, - Color::from_rgba8(r, g, b, a as f32 / 255.0), - ); - } - }); - - let duration = instant.elapsed(); - log::trace!("redraw: {:?}", duration); } fn on_event( @@ -196,57 +150,62 @@ where let state = tree.state.downcast_mut::(); let mut buffer = self.buffer.lock().unwrap(); + let layout_w = layout.bounds().width as i32; + let layout_h = layout.bounds().height as i32; + buffer.set_size(layout_w, layout_h); + + let mut status = Status::Ignored; match event { Event::Keyboard(KeyEvent::KeyPressed { key_code, modifiers }) => match key_code { KeyCode::Left => { buffer.action(TextAction::Left); - return Status::Captured; + status = Status::Captured; }, KeyCode::Right => { buffer.action(TextAction::Right); - return Status::Captured; + status = Status::Captured; }, KeyCode::Up => { buffer.action(TextAction::Up); - return Status::Captured; + status = Status::Captured; }, KeyCode::Down => { buffer.action(TextAction::Down); - return Status::Captured; + status = Status::Captured; }, KeyCode::Home => { buffer.action(TextAction::Home); - return Status::Captured; + status = Status::Captured; }, KeyCode::End => { buffer.action(TextAction::End); - return Status::Captured; + status = Status::Captured; }, KeyCode::PageUp => { buffer.action(TextAction::PageUp); - return Status::Captured; + status = Status::Captured; }, KeyCode::PageDown => { buffer.action(TextAction::PageDown); - return Status::Captured; + status = Status::Captured; }, KeyCode::Enter => { buffer.action(TextAction::Enter); - return Status::Captured; + status = Status::Captured; }, KeyCode::Backspace => { buffer.action(TextAction::Backspace); - return Status::Captured; + status = Status::Captured; }, KeyCode::Delete => { buffer.action(TextAction::Delete); - return Status::Captured; + status = Status::Captured; }, _ => () }, Event::Keyboard(KeyEvent::CharacterReceived(character)) => { buffer.action(TextAction::Insert(character)); - return Status::Captured; + status = Status::Captured; }, Event::Mouse(MouseEvent::ButtonPressed(Button::Left)) => { if layout.bounds().contains(cursor_position) { @@ -255,12 +214,12 @@ where y: (cursor_position.y - layout.bounds().y) as i32, }); state.is_dragging = true; - return Status::Captured; + status = Status::Captured; } }, Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => { state.is_dragging = false; - return Status::Captured; + status = Status::Captured; }, Event::Mouse(MouseEvent::CursorMoved { .. }) => { if state.is_dragging { @@ -268,7 +227,7 @@ where x: (cursor_position.x - layout.bounds().x) as i32, y: (cursor_position.y - layout.bounds().y) as i32, }); - return Status::Captured; + status = Status::Captured; } }, Event::Mouse(MouseEvent::WheelScrolled { delta }) => match delta { @@ -276,20 +235,95 @@ where buffer.action(TextAction::Scroll { lines: (-y * 6.0) as i32, }); - return Status::Captured; + status = Status::Captured; }, _ => (), }, _ => () } - Status::Ignored + if buffer.cursor_moved { + buffer.shape_until_cursor(); + buffer.cursor_moved = false; + } else { + buffer.shape_until_scroll(); + } + + if layout_w < 0 || layout_h < 0 { + // Invalid size, clear pixels + self.pixels_opt = None; + } else if buffer.redraw { + // Redraw buffer to image + + let instant = Instant::now(); + + let mut pixels = vec![0; layout_w as usize * layout_h as usize * 4]; + + //TODO: load from theme somehow + let text_color = cosmic_text::Color::rgb(0xFF, 0xFF, 0xFF); + buffer.draw(&mut self.cache.lock().unwrap(), text_color, |start_x, start_y, w, h, color| { + let alpha = (color.0 >> 24) & 0xFF; + if alpha == 0 { + // Do not draw if alpha is zero + return; + } + + for y in start_y..start_y + h as i32{ + if y < 0 || y >= layout_h { + // Skip if y out of bounds + continue; + } + + let offset_y = y as usize * layout_w as usize * 4; + for x in start_x..start_x + w as i32 { + if x < 0 || x >= layout_w { + // Skip if x out of bounds + continue; + } + + let offset = offset_y + x as usize * 4; + + let mut current = + pixels[offset + 2] as u32 | + (pixels[offset + 1] as u32) << 8 | + (pixels[offset] as u32) << 16 | + (pixels[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); + } + + pixels[offset + 2] = current as u8; + pixels[offset + 1] = (current >> 8) as u8; + pixels[offset] = (current >> 16) as u8; + pixels[offset + 3] = (current >> 24) as u8; + } + } + }); + + self.pixels_opt = Some((layout_w as u32, layout_h as u32, pixels)); + + buffer.redraw = false; + + let duration = instant.elapsed(); + log::debug!("redraw: {:?}", duration); + } + + status } } impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: renderer::Renderer, + Renderer: renderer::Renderer + image::Renderer, Renderer::Theme: StyleSheet, { fn from(text_box: TextBox<'a>) -> Self {