Merge libcosmic examples - iced fork supports pixel perfect images

This commit is contained in:
Jeremy Soller 2022-10-27 15:06:00 -06:00
parent 52be09ec17
commit 5d38951338
No known key found for this signature in database
GPG key ID: 87F211AF2BE4C2FE
5 changed files with 113 additions and 719 deletions

View file

@ -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<Color>,
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<TextBuffer<'static>>,
cache: &'a Mutex<SwashCache<'static>>,
pixels_opt: Option<(u32, u32, Vec<u8>)>,
}
impl<'a> TextBox<'a> {
pub fn new(buffer: &'a Mutex<TextBuffer<'static>>, cache: &'a Mutex<SwashCache<'static>>) -> Self {
Self { buffer, cache }
Self {
buffer,
cache,
pixels_opt: None,
}
}
}
@ -65,7 +67,7 @@ pub fn text_box<'a>(buffer: &'a Mutex<TextBuffer<'static>>, cache: &'a Mutex<Swa
impl<'a, Message, Renderer> Widget<Message, Renderer> for TextBox<'a>
where
Renderer: renderer::Renderer,
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
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::<State>();
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<TextBox<'a>> for Element<'a, Message, Renderer>
where
Renderer: renderer::Renderer,
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
Renderer::Theme: StyleSheet,
{
fn from(text_box: TextBox<'a>) -> Self {