Remove editor-libcosmic
This commit is contained in:
parent
8174877407
commit
0a24ee0423
5 changed files with 6 additions and 815 deletions
|
|
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Remove editor-libcosmic, see cosmic-edit instead
|
||||||
|
|
||||||
## [0.11.0] - 2024-02-07
|
## [0.11.0] - 2024-02-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# SPDX-License-Identifier: MIT OR Apache-2.0
|
|
||||||
|
|
||||||
RUST_LOG="cosmic_text=debug,editor_libcosmic=debug" cargo run --release --package editor-libcosmic -- "$@"
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "editor-libcosmic"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
|
||||||
edition = "2021"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cosmic-text = { path = "../../", features = ["syntect", "vi"] }
|
|
||||||
env_logger = "0.10"
|
|
||||||
fontdb = "0.13"
|
|
||||||
lazy_static = "1.4"
|
|
||||||
log = "0.4"
|
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
|
||||||
git = "https://github.com/pop-os/libcosmic"
|
|
||||||
rev = "e3f30a1"
|
|
||||||
#path = "../../../libcosmic"
|
|
||||||
|
|
||||||
[dependencies.rfd]
|
|
||||||
version = "0.11"
|
|
||||||
#TODO: iced portal
|
|
||||||
#default-features = false
|
|
||||||
#features = ["xdg-portal"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
vi = []
|
|
||||||
|
|
@ -1,397 +0,0 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
||||||
|
|
||||||
use cosmic::{
|
|
||||||
iced::{
|
|
||||||
self,
|
|
||||||
widget::{column, horizontal_space, pick_list, row},
|
|
||||||
Alignment, Application, Color, Command, Length,
|
|
||||||
},
|
|
||||||
settings,
|
|
||||||
theme::{self, Theme, ThemeType},
|
|
||||||
widget::{button, text, toggler},
|
|
||||||
Element,
|
|
||||||
};
|
|
||||||
use cosmic_text::{
|
|
||||||
Align, Attrs, AttrsList, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, Wrap,
|
|
||||||
};
|
|
||||||
use std::{env, fmt, fs, path::PathBuf, sync::Mutex};
|
|
||||||
|
|
||||||
use self::text_box::text_box;
|
|
||||||
mod text_box;
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref FONT_SYSTEM: Mutex<FontSystem> = Mutex::new(FontSystem::new());
|
|
||||||
static ref SYNTAX_SYSTEM: SyntaxSystem = SyntaxSystem::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum FontSize {
|
|
||||||
Caption,
|
|
||||||
Body,
|
|
||||||
Title4,
|
|
||||||
Title3,
|
|
||||||
Title2,
|
|
||||||
Title1,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FontSize {
|
|
||||||
pub fn all() -> &'static [Self] {
|
|
||||||
&[
|
|
||||||
Self::Caption,
|
|
||||||
Self::Body,
|
|
||||||
Self::Title4,
|
|
||||||
Self::Title3,
|
|
||||||
Self::Title2,
|
|
||||||
Self::Title1,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_metrics(self) -> Metrics {
|
|
||||||
match self {
|
|
||||||
Self::Caption => Metrics::new(10.0, 14.0), // Caption
|
|
||||||
Self::Body => Metrics::new(14.0, 20.0), // Body
|
|
||||||
Self::Title4 => Metrics::new(20.0, 28.0), // Title 4
|
|
||||||
Self::Title3 => Metrics::new(24.0, 32.0), // Title 3
|
|
||||||
Self::Title2 => Metrics::new(28.0, 36.0), // Title 2
|
|
||||||
Self::Title1 => Metrics::new(32.0, 44.0), // Title 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for FontSize {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Caption => write!(f, "Caption"),
|
|
||||||
Self::Body => write!(f, "Body"),
|
|
||||||
Self::Title4 => write!(f, "Title 4"),
|
|
||||||
Self::Title3 => write!(f, "Title 3"),
|
|
||||||
Self::Title2 => write!(f, "Title 2"),
|
|
||||||
Self::Title1 => write!(f, "Title 1"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static WRAP_MODE: &[Wrap] = &[Wrap::None, Wrap::Glyph, Wrap::Word];
|
|
||||||
|
|
||||||
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<PathBuf>,
|
|
||||||
attrs: Attrs<'static>,
|
|
||||||
font_size: FontSize,
|
|
||||||
editor: Mutex<cosmic_text::ViEditor<'static, 'static>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum Message {
|
|
||||||
Open,
|
|
||||||
Save,
|
|
||||||
Bold(bool),
|
|
||||||
Italic(bool),
|
|
||||||
Monospaced(bool),
|
|
||||||
FontSizeChanged(FontSize),
|
|
||||||
WrapChanged(Wrap),
|
|
||||||
AlignmentChanged(Align),
|
|
||||||
ThemeChanged(&'static str),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub fn open(&mut self, path: PathBuf) {
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
|
||||||
let mut font_system = FONT_SYSTEM.lock().unwrap();
|
|
||||||
let mut editor = editor.borrow_with(&mut font_system);
|
|
||||||
match editor.load_text(&path, self.attrs) {
|
|
||||||
Ok(()) => {
|
|
||||||
log::info!("opened '{}'", path.display());
|
|
||||||
self.path_opt = Some(path);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("failed to open '{}': {}", path.display(), err);
|
|
||||||
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<Self::Message>) {
|
|
||||||
let attrs = cosmic_text::Attrs::new().family(cosmic_text::Family::Monospace);
|
|
||||||
|
|
||||||
let editor = SyntaxEditor::new(
|
|
||||||
Buffer::new(
|
|
||||||
&mut FONT_SYSTEM.lock().unwrap(),
|
|
||||||
FontSize::Body.to_metrics(),
|
|
||||||
),
|
|
||||||
&SYNTAX_SYSTEM,
|
|
||||||
"base16-eighties.dark",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut editor = cosmic_text::ViEditor::new(editor);
|
|
||||||
editor.set_passthrough(cfg!(feature = "vi"));
|
|
||||||
|
|
||||||
update_attrs(&mut editor, attrs);
|
|
||||||
|
|
||||||
let mut window = Window {
|
|
||||||
theme: Theme::dark(),
|
|
||||||
font_size: FontSize::Body,
|
|
||||||
path_opt: None,
|
|
||||||
attrs,
|
|
||||||
editor: Mutex::new(editor),
|
|
||||||
};
|
|
||||||
if let Some(arg) = env::args().nth(1) {
|
|
||||||
window.open(PathBuf::from(arg));
|
|
||||||
}
|
|
||||||
(window, Command::none())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn theme(&self) -> Theme {
|
|
||||||
self.theme.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn title(&self) -> String {
|
|
||||||
if let Some(path) = &self.path_opt {
|
|
||||||
format!(
|
|
||||||
"COSMIC Text - {} - {}",
|
|
||||||
FONT_SYSTEM.lock().unwrap().locale(),
|
|
||||||
path.display()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!("COSMIC Text - {}", FONT_SYSTEM.lock().unwrap().locale())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
|
|
||||||
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 editor = self.editor.lock().unwrap();
|
|
||||||
let mut text = String::new();
|
|
||||||
editor.with_buffer(|buffer| {
|
|
||||||
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 editor = self.editor.lock().unwrap();
|
|
||||||
update_attrs(&mut *editor, self.attrs);
|
|
||||||
}
|
|
||||||
Message::Italic(italic) => {
|
|
||||||
self.attrs = self.attrs.style(if italic {
|
|
||||||
cosmic_text::Style::Italic
|
|
||||||
} else {
|
|
||||||
cosmic_text::Style::Normal
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
|
||||||
update_attrs(&mut *editor, self.attrs);
|
|
||||||
}
|
|
||||||
Message::Monospaced(monospaced) => {
|
|
||||||
self.attrs = self.attrs.family(if monospaced {
|
|
||||||
cosmic_text::Family::Monospace
|
|
||||||
} else {
|
|
||||||
cosmic_text::Family::SansSerif
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
|
||||||
update_attrs(&mut *editor, self.attrs);
|
|
||||||
}
|
|
||||||
Message::FontSizeChanged(font_size) => {
|
|
||||||
self.font_size = font_size;
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
|
||||||
editor
|
|
||||||
.borrow_with(&mut FONT_SYSTEM.lock().unwrap())
|
|
||||||
.with_buffer_mut(|buffer| buffer.set_metrics(font_size.to_metrics()));
|
|
||||||
}
|
|
||||||
Message::WrapChanged(wrap) => {
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
|
||||||
editor
|
|
||||||
.borrow_with(&mut FONT_SYSTEM.lock().unwrap())
|
|
||||||
.with_buffer_mut(|buffer| buffer.set_wrap(wrap));
|
|
||||||
}
|
|
||||||
Message::AlignmentChanged(align) => {
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
|
||||||
update_alignment(&mut *editor, align);
|
|
||||||
}
|
|
||||||
Message::ThemeChanged(theme) => {
|
|
||||||
self.theme = match theme {
|
|
||||||
"Dark" => Theme::dark(),
|
|
||||||
"Light" => Theme::light(),
|
|
||||||
_ => return Command::none(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let Color { r, g, b, a } = self.theme.cosmic().on_bg_color().into();
|
|
||||||
let as_u8 = |component: f32| (component * 255.0) as u8;
|
|
||||||
self.attrs = self.attrs.color(cosmic_text::Color::rgba(
|
|
||||||
as_u8(r),
|
|
||||||
as_u8(g),
|
|
||||||
as_u8(b),
|
|
||||||
as_u8(a),
|
|
||||||
));
|
|
||||||
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
|
||||||
|
|
||||||
#[cfg(not(feature = "vi"))]
|
|
||||||
// Update the syntax color theme
|
|
||||||
match theme {
|
|
||||||
"Light" => editor.update_theme("base16-ocean.light"),
|
|
||||||
"Dark" | _ => editor.update_theme("base16-eighties.dark"),
|
|
||||||
};
|
|
||||||
|
|
||||||
update_attrs(&mut *editor, self.attrs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Command::none()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
|
||||||
static THEMES: &[&str] = &["Dark", "Light"];
|
|
||||||
let theme_picker = pick_list(
|
|
||||||
THEMES,
|
|
||||||
Some(match self.theme.theme_type {
|
|
||||||
ThemeType::Dark => THEMES[0],
|
|
||||||
ThemeType::Light => THEMES[1],
|
|
||||||
_ => unreachable!(),
|
|
||||||
}),
|
|
||||||
Message::ThemeChanged,
|
|
||||||
);
|
|
||||||
|
|
||||||
let font_size_picker = {
|
|
||||||
pick_list(
|
|
||||||
FontSize::all(),
|
|
||||||
Some(self.font_size),
|
|
||||||
Message::FontSizeChanged,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let wrap_picker = {
|
|
||||||
let editor = self.editor.lock().unwrap();
|
|
||||||
pick_list(
|
|
||||||
WRAP_MODE,
|
|
||||||
Some(editor.with_buffer(|buffer| buffer.wrap())),
|
|
||||||
Message::WrapChanged,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let content: Element<_> = column![
|
|
||||||
row![
|
|
||||||
button(theme::Button::Secondary)
|
|
||||||
.text("Open")
|
|
||||||
.on_press(Message::Open),
|
|
||||||
button(theme::Button::Secondary)
|
|
||||||
.text("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.family == cosmic_text::Family::Monospace,
|
|
||||||
Message::Monospaced
|
|
||||||
),
|
|
||||||
text("Theme:"),
|
|
||||||
theme_picker,
|
|
||||||
text("Font Size:"),
|
|
||||||
font_size_picker,
|
|
||||||
]
|
|
||||||
.align_items(Alignment::Center)
|
|
||||||
.spacing(8),
|
|
||||||
row![
|
|
||||||
text("Wrap:"),
|
|
||||||
wrap_picker,
|
|
||||||
button(theme::Button::Text)
|
|
||||||
.icon(theme::Svg::Default, "format-justify-left", 20)
|
|
||||||
.on_press(Message::AlignmentChanged(Align::Left)),
|
|
||||||
button(theme::Button::Text)
|
|
||||||
.icon(theme::Svg::Symbolic, "format-justify-center", 20)
|
|
||||||
.on_press(Message::AlignmentChanged(Align::Center)),
|
|
||||||
button(theme::Button::Text)
|
|
||||||
.icon(theme::Svg::Symbolic, "format-justify-right", 20)
|
|
||||||
.on_press(Message::AlignmentChanged(Align::Right)),
|
|
||||||
button(theme::Button::Text)
|
|
||||||
.icon(theme::Svg::SymbolicLink, "format-justify-fill", 20)
|
|
||||||
.on_press(Message::AlignmentChanged(Align::Justified)),
|
|
||||||
]
|
|
||||||
.align_items(Alignment::Center)
|
|
||||||
.spacing(8),
|
|
||||||
text_box(&self.editor)
|
|
||||||
]
|
|
||||||
.spacing(8)
|
|
||||||
.padding(16)
|
|
||||||
.into();
|
|
||||||
|
|
||||||
// Uncomment to debug layout: content.explain(Color::WHITE)
|
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_attrs<'buffer, E: Edit<'buffer>>(editor: &mut E, attrs: Attrs) {
|
|
||||||
editor.with_buffer_mut(|buffer| {
|
|
||||||
buffer.lines.iter_mut().for_each(|line| {
|
|
||||||
line.set_attrs_list(AttrsList::new(attrs));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_alignment<'buffer, E: Edit<'buffer>>(editor: &mut E, align: Align) {
|
|
||||||
let current_line = editor.cursor().line;
|
|
||||||
let selection_bounds_opt = editor.selection_bounds();
|
|
||||||
editor.with_buffer_mut(|buffer| {
|
|
||||||
if let Some((start, end)) = selection_bounds_opt {
|
|
||||||
if let Some(lines) = buffer.lines.get_mut(start.line..=end.line) {
|
|
||||||
for line in lines.iter_mut() {
|
|
||||||
line.set_align(Some(align));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Some(line) = buffer.lines.get_mut(current_line) {
|
|
||||||
line.set_align(Some(align));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,386 +0,0 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
||||||
|
|
||||||
use cosmic::{
|
|
||||||
iced_core::{event::Status, widget::tree, *},
|
|
||||||
iced_runtime::keyboard::KeyCode,
|
|
||||||
theme::{Theme, ThemeType},
|
|
||||||
};
|
|
||||||
use cosmic_text::{Action, Edit, Motion, SwashCache, ViEditor};
|
|
||||||
use std::{cmp, sync::Mutex, time::Instant};
|
|
||||||
|
|
||||||
use crate::FONT_SYSTEM;
|
|
||||||
|
|
||||||
pub struct Appearance {
|
|
||||||
background_color: Option<Color>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait StyleSheet {
|
|
||||||
fn appearance(&self) -> Appearance;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StyleSheet for Theme {
|
|
||||||
fn appearance(&self) -> Appearance {
|
|
||||||
match self.theme_type {
|
|
||||||
ThemeType::Dark | ThemeType::HighContrastDark | ThemeType::Custom(_) => Appearance {
|
|
||||||
background_color: Some(Color::from_rgb8(0x34, 0x34, 0x34)),
|
|
||||||
},
|
|
||||||
ThemeType::Light | ThemeType::HighContrastLight => Appearance {
|
|
||||||
background_color: Some(Color::from_rgb8(0xFC, 0xFC, 0xFC)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextBox<'a, 'editor, 'buffer> {
|
|
||||||
editor: &'a Mutex<ViEditor<'editor, 'buffer>>,
|
|
||||||
padding: Padding,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'editor, 'buffer> TextBox<'a, 'editor, 'buffer> {
|
|
||||||
pub fn new(editor: &'a Mutex<ViEditor<'editor, 'buffer>>) -> Self {
|
|
||||||
Self {
|
|
||||||
editor,
|
|
||||||
padding: Padding::new(0.),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
|
|
||||||
self.padding = padding.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_box<'a, 'editor, 'buffer>(
|
|
||||||
editor: &'a Mutex<ViEditor<'editor, 'buffer>>,
|
|
||||||
) -> TextBox<'a, 'editor, 'buffer> {
|
|
||||||
TextBox::new(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, 'buffer, Message, Renderer> Widget<Message, Renderer>
|
|
||||||
for TextBox<'a, 'editor, 'buffer>
|
|
||||||
where
|
|
||||||
Renderer: cosmic::iced_core::Renderer + image::Renderer<Handle = image::Handle>,
|
|
||||||
Renderer::Theme: StyleSheet,
|
|
||||||
{
|
|
||||||
fn tag(&self) -> tree::Tag {
|
|
||||||
tree::Tag::of::<State>()
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
let limits = limits.width(Length::Fill).height(Length::Fill);
|
|
||||||
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
|
||||||
editor
|
|
||||||
.borrow_with(&mut FONT_SYSTEM.lock().unwrap())
|
|
||||||
.shape_as_needed(true);
|
|
||||||
|
|
||||||
let mut layout_lines = 0;
|
|
||||||
editor.with_buffer(|buffer| {
|
|
||||||
for line in buffer.lines.iter() {
|
|
||||||
match line.layout_opt() {
|
|
||||||
Some(layout) => layout_lines += layout.len(),
|
|
||||||
None => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let height =
|
|
||||||
layout_lines as f32 * editor.with_buffer(|buffer| buffer.metrics().line_height);
|
|
||||||
let size = Size::new(limits.max().width, height);
|
|
||||||
|
|
||||||
layout::Node::new(limits.resolve(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mouse_interaction(
|
|
||||||
&self,
|
|
||||||
_tree: &widget::Tree,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor_position: mouse::Cursor,
|
|
||||||
_viewport: &Rectangle,
|
|
||||||
_renderer: &Renderer,
|
|
||||||
) -> mouse::Interaction {
|
|
||||||
if cursor_position.is_over(layout.bounds()) {
|
|
||||||
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: mouse::Cursor,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) {
|
|
||||||
let instant = Instant::now();
|
|
||||||
|
|
||||||
let state = tree.state.downcast_ref::<State>();
|
|
||||||
|
|
||||||
let appearance = theme.appearance();
|
|
||||||
|
|
||||||
if let Some(background_color) = appearance.background_color {
|
|
||||||
renderer.fill_quad(
|
|
||||||
renderer::Quad {
|
|
||||||
bounds: layout.bounds(),
|
|
||||||
border_radius: 0.0.into(),
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: Color::TRANSPARENT,
|
|
||||||
},
|
|
||||||
background_color,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
const SCALE_FACTOR: f64 = 1.;
|
|
||||||
|
|
||||||
let image_w = (view_w as f64 * SCALE_FACTOR) as i32;
|
|
||||||
let image_h = (view_h as f64 * SCALE_FACTOR) as i32;
|
|
||||||
|
|
||||||
let mut font_system = FONT_SYSTEM.lock().unwrap();
|
|
||||||
let mut editor = editor.borrow_with(&mut font_system);
|
|
||||||
|
|
||||||
// Scale metrics
|
|
||||||
let metrics = editor.with_buffer(|buffer| buffer.metrics());
|
|
||||||
editor.with_buffer_mut(|buffer| buffer.set_metrics(metrics.scale(SCALE_FACTOR as f32)));
|
|
||||||
|
|
||||||
// Set size
|
|
||||||
editor.with_buffer_mut(|buffer| buffer.set_size(image_w as f32, image_h as f32));
|
|
||||||
|
|
||||||
// Shape and layout
|
|
||||||
editor.shape_as_needed(true);
|
|
||||||
|
|
||||||
// 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(), |x, y, w, h, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Restore original metrics
|
|
||||||
editor.with_buffer_mut(|buffer| buffer.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 as f32, view_h as f32),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let duration = instant.elapsed();
|
|
||||||
log::debug!("redraw {}, {}: {:?}", view_w, view_h, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_event(
|
|
||||||
&mut self,
|
|
||||||
tree: &mut widget::Tree,
|
|
||||||
event: Event,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor_position: mouse::Cursor,
|
|
||||||
_renderer: &Renderer,
|
|
||||||
_clipboard: &mut dyn Clipboard,
|
|
||||||
_shell: &mut Shell<'_, Message>,
|
|
||||||
) -> Status {
|
|
||||||
let state = tree.state.downcast_mut::<State>();
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
|
||||||
let mut font_system = FONT_SYSTEM.lock().unwrap();
|
|
||||||
let mut editor = editor.borrow_with(&mut font_system);
|
|
||||||
|
|
||||||
let mut status = Status::Ignored;
|
|
||||||
match event {
|
|
||||||
Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => match key_code {
|
|
||||||
KeyCode::Left => {
|
|
||||||
editor.action(Action::Motion(Motion::Left));
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
KeyCode::Right => {
|
|
||||||
editor.action(Action::Motion(Motion::Right));
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
KeyCode::Up => {
|
|
||||||
editor.action(Action::Motion(Motion::Up));
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
KeyCode::Down => {
|
|
||||||
editor.action(Action::Motion(Motion::Down));
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
KeyCode::Home => {
|
|
||||||
editor.action(Action::Motion(Motion::Home));
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
KeyCode::End => {
|
|
||||||
editor.action(Action::Motion(Motion::End));
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
KeyCode::PageUp => {
|
|
||||||
editor.action(Action::Motion(Motion::PageUp));
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
KeyCode::PageDown => {
|
|
||||||
editor.action(Action::Motion(Motion::PageDown));
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
KeyCode::Escape => {
|
|
||||||
editor.action(Action::Escape);
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
editor.action(Action::Enter);
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
editor.action(Action::Backspace);
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
KeyCode::Delete => {
|
|
||||||
editor.action(Action::Delete);
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
Event::Keyboard(keyboard::Event::CharacterReceived(character)) => {
|
|
||||||
editor.action(Action::Insert(character));
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
|
||||||
if let Some(position_in) = cursor_position.position_in(layout.bounds()) {
|
|
||||||
editor.action(Action::Click {
|
|
||||||
x: position_in.x as i32 - self.padding.left as i32,
|
|
||||||
y: position_in.y as i32 - self.padding.top as i32,
|
|
||||||
});
|
|
||||||
state.is_dragging = true;
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
|
|
||||||
state.is_dragging = false;
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
Event::Mouse(mouse::Event::CursorMoved { position }) => {
|
|
||||||
if state.is_dragging {
|
|
||||||
editor.action(Action::Drag {
|
|
||||||
x: (position.x - layout.bounds().x) as i32 - self.padding.left as i32,
|
|
||||||
y: (position.y - layout.bounds().y) as i32 - self.padding.top as i32,
|
|
||||||
});
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::Mouse(mouse::Event::WheelScrolled { delta }) => match delta {
|
|
||||||
mouse::ScrollDelta::Lines { y, .. } => {
|
|
||||||
editor.action(Action::Scroll {
|
|
||||||
lines: (-y * 6.0) as i32,
|
|
||||||
});
|
|
||||||
status = Status::Captured;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'editor, 'buffer, Message, Renderer> From<TextBox<'a, 'editor, 'buffer>>
|
|
||||||
for Element<'a, Message, Renderer>
|
|
||||||
where
|
|
||||||
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
|
|
||||||
Renderer::Theme: StyleSheet,
|
|
||||||
{
|
|
||||||
fn from(text_box: TextBox<'a, 'editor, 'buffer>) -> Self {
|
|
||||||
Self::new(text_box)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct State {
|
|
||||||
is_dragging: bool,
|
|
||||||
cache: Mutex<SwashCache>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
/// Creates a new [`State`].
|
|
||||||
pub fn new() -> State {
|
|
||||||
State {
|
|
||||||
is_dragging: false,
|
|
||||||
cache: Mutex::new(SwashCache::new()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue