Text library moved from libcosmic

This commit is contained in:
Jeremy Soller 2022-10-18 12:07:22 -06:00
commit 410d4ee674
No known key found for this signature in database
GPG key ID: 87F211AF2BE4C2FE
37 changed files with 12909 additions and 0 deletions

View file

@ -0,0 +1,22 @@
[package]
name = "editor-libcosmic"
version = "0.1.0"
authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2021"
license = "MPL2"
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"
[features]
mono = []

View file

@ -0,0 +1,139 @@
use cosmic::{
iced::widget::{
container,
},
iced::{
self,
Application,
Command,
Element,
Theme
},
settings,
};
use cosmic_text::{
FontMatches,
FontSystem,
TextBuffer,
};
use std::{
env,
fs,
sync::{Arc, RwLock},
};
use self::text_box::text_box;
mod text_box;
lazy_static::lazy_static! {
static ref FONT_SYSTEM: FontSystem = FontSystem::new();
}
//TODO: find out how to do this!
static mut FONT_MATCHES: Option<FontMatches<'static>> = None;
fn main() -> cosmic::iced::Result {
env_logger::init();
let font_matches: FontMatches<'static> = FONT_SYSTEM.matches(|info| -> bool {
#[cfg(feature = "mono")]
let monospaced = true;
#[cfg(not(feature = "mono"))]
let monospaced = false;
let matched = {
info.style == fontdb::Style::Normal &&
info.weight == fontdb::Weight::NORMAL &&
info.stretch == fontdb::Stretch::Normal &&
(info.monospaced == monospaced || info.post_script_name.contains("Emoji"))
};
if matched {
log::debug!(
"{:?}: family '{}' postscript name '{}' style {:?} weight {:?} stretch {:?} monospaced {:?}",
info.id,
info.family,
info.post_script_name,
info.style,
info.weight,
info.stretch,
info.monospaced
);
}
matched
}).unwrap();
unsafe { FONT_MATCHES = Some(font_matches); }
let mut settings = settings();
settings.window.min_size = Some((400, 100));
Window::run(settings)
}
pub struct Window {
buffer: Arc<RwLock<TextBuffer<'static>>>,
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub enum Message {}
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 font_sizes = [
(10, 14), // Caption
(14, 20), // Body
(20, 28), // Title 4
(24, 32), // Title 3
(28, 36), // Title 2
(32, 44), // Title 1
];
let font_size_default = 1; // Body
let mut font_size_i = font_size_default;
let text = if let Some(arg) = env::args().nth(1) {
fs::read_to_string(&arg).expect("failed to open file")
} else {
#[cfg(feature = "mono")]
let default_text = include_str!("../../../sample/mono.txt");
#[cfg(not(feature = "mono"))]
let default_text = include_str!("../../../sample/proportional.txt");
default_text.to_string()
};
let line_x = 8;
let buffer = Arc::new(RwLock::new(TextBuffer::new(
unsafe { FONT_MATCHES.as_ref().unwrap() },
&text,
font_sizes[font_size_i].0,
600 - line_x * 2,
)));
let window = Window {
buffer,
};
(window, Command::none())
}
fn title(&self) -> String {
let buffer = self.buffer.read().unwrap();
format!("COSMIC Text - iced - {}", buffer.font_matches().locale)
}
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
Command::none()
}
fn view(&self) -> Element<Message> {
container(
text_box(self.buffer.clone())
).padding(16).into()
}
}

View file

@ -0,0 +1,272 @@
use cosmic::iced_native::{
{Color, Element, Length, Point, Rectangle, Size, Shell},
clipboard::Clipboard,
event::{
Event,
Status,
},
keyboard::{Event as KeyEvent, KeyCode},
layout::{self, Layout},
renderer,
widget::{self, Widget},
};
use cosmic_text::{
FontLineIndex,
TextAction,
TextBuffer,
};
use std::{
sync::{Arc, RwLock},
time::Instant,
};
pub struct TextBox<'a> {
buffer: Arc<RwLock<TextBuffer<'a>>>,
}
impl<'a> TextBox<'a> {
pub fn new(buffer: Arc<RwLock<TextBuffer<'a>>>) -> Self {
Self { buffer }
}
}
pub fn text_box<'a>(buffer: Arc<RwLock<TextBuffer<'a>>>) -> TextBox<'a> {
TextBox::new(buffer)
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for TextBox<'a>
where
Renderer: renderer::Renderer,
{
fn width(&self) -> Length {
Length::Shrink
}
fn height(&self) -> Length {
Length::Shrink
}
fn layout(
&self,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
println!("{:?}", limits);
let size = limits.max();
{
let mut buffer = self.buffer.write().unwrap();
let font_size = buffer.font_size();
let line_height = font_size + 8;
buffer.set_line_width(size.width as i32);
buffer.shape_until(size.height as i32 / line_height);
}
layout::Node::new(size)
}
fn draw(
&self,
_state: &widget::Tree,
renderer: &mut Renderer,
_theme: &Renderer::Theme,
_style: &renderer::Style,
layout: Layout<'_>,
_cursor_position: Point,
_viewport: &Rectangle,
) {
let buffer = self.buffer.read().unwrap();
let font_size = buffer.font_size();
let line_height = font_size + 8; /*TODO: store somewhere else */
let scroll = 0;
let instant = Instant::now();
renderer.fill_quad(
renderer::Quad {
bounds: layout.bounds(),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color::from_rgb8(0x34, 0x34, 0x34),
);
let line_x = layout.bounds().x as i32;
let mut line_y = layout.bounds().y as i32 + line_height;
let mut start_line_opt = None;
let mut end_line = FontLineIndex::new(0);
for (line_i, line) in buffer
.layout_lines()
.iter()
.skip(scroll as usize)
.enumerate()
{
if line_y >= (layout.bounds().y + layout.bounds().height) as i32 {
break;
}
end_line = line.line_i;
if start_line_opt == None {
start_line_opt = Some(end_line);
}
if buffer.cursor.line == line_i + scroll as usize {
if buffer.cursor.glyph >= line.glyphs.len() {
let x = match line.glyphs.last() {
Some(glyph) => glyph.x + glyph.w,
None => 0.0,
};
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle::new(
Point::new(line_x as f32 + x, (line_y - font_size) as f32),
Size::new((font_size / 2) as f32, line_height as f32)
),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.125),
);
} else {
let glyph = &line.glyphs[buffer.cursor.glyph];
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle::new(
Point::new(line_x as f32 + glyph.x, (line_y - font_size) as f32),
Size::new(glyph.w, line_height as f32)
),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.125),
);
let text_line = &buffer.text_lines()[line.line_i.get()];
log::info!(
"{}, {}: '{}' ('{}'): '{}'",
glyph.start,
glyph.end,
glyph.font.info.family,
glyph.font.info.post_script_name,
&text_line[glyph.start..glyph.end],
);
}
}
line.draw(0xFFFFFF, |x, y, data| {
let a = (data >> 24) as u8;
if a > 0 {
let r = (data >> 16) as u8;
let g = (data >> 8) as u8;
let b = data as u8;
let bounds = Rectangle::new(
Point::new((line_x + x) as f32, (line_y + y) as f32),
Size::new(1.0, 1.0)
);
let color = Color::from_rgba8(r, g, b, a as f32 / 255.0);
renderer.fill_quad(
renderer::Quad {
bounds,
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
color
);
}
});
line_y += line_height;
}
/*
// Draw scrollbar
{
let start_line = start_line_opt.unwrap_or(end_line);
let lines = buffer.text_lines().len();
let start_y = (start_line.get() * window.height() as usize) / lines;
let end_y = (end_line.get() * window.height() as usize) / lines;
if end_y > start_y {
window.rect(
window.width() as i32 - line_x as i32,
start_y as i32,
line_x as u32,
(end_y - start_y) as u32,
Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.25),
);
}
}
buffer.redraw = false;
*/
let duration = instant.elapsed();
log::debug!("redraw: {:?}", duration);
}
fn on_event(
&mut self,
_state: &mut widget::Tree,
event: Event,
_layout: Layout<'_>,
_cursor_position: Point,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
) -> Status {
let mut buffer = self.buffer.write().unwrap();
match event {
Event::Keyboard(key_event) => match key_event {
KeyEvent::KeyPressed { key_code, modifiers } => {
match key_code {
KeyCode::Left => {
buffer.action(TextAction::Left);
Status::Captured
},
KeyCode::Right => {
buffer.action(TextAction::Right);
Status::Captured
},
KeyCode::Up => {
buffer.action(TextAction::Up);
Status::Captured
},
KeyCode::Down => {
buffer.action(TextAction::Down);
Status::Captured
},
KeyCode::Backspace => {
buffer.action(TextAction::Backspace);
Status::Captured
},
KeyCode::Delete => {
buffer.action(TextAction::Delete);
Status::Captured
},
_ => Status::Ignored,
}
},
KeyEvent::CharacterReceived(character) => {
buffer.action(TextAction::Insert(character));
Status::Captured
},
_ => Status::Ignored,
},
_ => Status::Ignored,
}
}
}
impl<'a, Message, Renderer> From<TextBox<'a>> for Element<'a, Message, Renderer>
where
Renderer: renderer::Renderer,
{
fn from(text_box: TextBox<'a>) -> Self {
Self::new(text_box)
}
}

View file

@ -0,0 +1,17 @@
[package]
name = "editor-orbclient"
version = "0.1.0"
authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2021"
license = "MPL2"
publish = false
[dependencies]
cosmic-text = { path = "../../" }
env_logger = "0.9"
fontdb = "0.9"
log = "0.4"
orbclient = "0.3.35"
[features]
mono = []

View file

@ -0,0 +1,328 @@
use cosmic_text::{FontLineIndex, FontSystem, TextAction, TextBuffer, TextCursor};
use orbclient::{Color, EventOption, Renderer, Window, WindowFlag};
use std::{cmp, env, fs, time::Instant};
fn main() {
env_logger::init();
let display_scale = match orbclient::get_display_size() {
Ok((w, h)) => {
log::info!("Display size: {}, {}", w, h);
(h as i32 / 1600) + 1
}
Err(err) => {
log::warn!("Failed to get display size: {}", err);
1
}
};
let font_system = FontSystem::new();
let mut window = Window::new_flags(
-1,
-1,
1024 * display_scale as u32,
768 * display_scale as u32,
&format!("COSMIC TEXT - {}", font_system.locale),
&[WindowFlag::Resizable],
)
.unwrap();
let font_matches = font_system.matches(|info| -> bool {
#[cfg(feature = "mono")]
let monospaced = true;
#[cfg(not(feature = "mono"))]
let monospaced = false;
let matched = {
info.style == fontdb::Style::Normal &&
info.weight == fontdb::Weight::NORMAL &&
info.stretch == fontdb::Stretch::Normal &&
(info.monospaced == monospaced || info.post_script_name.contains("Emoji"))
};
if matched {
log::debug!(
"{:?}: family '{}' postscript name '{}' style {:?} weight {:?} stretch {:?} monospaced {:?}",
info.id,
info.family,
info.post_script_name,
info.style,
info.weight,
info.stretch,
info.monospaced
);
}
matched
}).unwrap();
let bg_color = Color::rgb(0x34, 0x34, 0x34);
let font_color = Color::rgb(0xFF, 0xFF, 0xFF);
let font_sizes = [
(10, 14), // Caption
(14, 20), // Body
(20, 28), // Title 4
(24, 32), // Title 3
(28, 36), // Title 2
(32, 44), // Title 1
];
let font_size_default = 1; // Body
let mut font_size_i = font_size_default;
let text = if let Some(arg) = env::args().nth(1) {
fs::read_to_string(&arg).expect("failed to open file")
} else {
#[cfg(feature = "mono")]
let default_text = include_str!("../../../sample/mono.txt");
#[cfg(not(feature = "mono"))]
let default_text = include_str!("../../../sample/proportional.txt");
default_text.to_string()
};
let line_x = 8 * display_scale;
let mut buffer = TextBuffer::new(
&font_matches,
&text,
font_sizes[font_size_i].0 * display_scale,
window.width() as i32 - line_x * 2,
);
let mut ctrl_pressed = false;
let mut mouse_x = -1;
let mut mouse_y = -1;
let mut mouse_left = false;
let mut rehit = false;
let mut scroll = 0;
loop {
let font_size = buffer.font_size();
let line_height = font_sizes[font_size_i].1 * display_scale;
let window_lines = (window.height() as i32 + line_height - 1) / line_height;
buffer.shape_until(scroll + window_lines);
scroll = cmp::max(
0,
cmp::min(
buffer.layout_lines().len() as i32 - (window_lines - 1),
scroll,
),
);
if rehit {
let instant = Instant::now();
let mut new_cursor_opt = None;
let mut line_y = line_height;
for (line_i, line) in buffer
.layout_lines()
.iter()
.skip(scroll as usize)
.enumerate()
{
if line_y >= window.height() as i32 {
break;
}
if mouse_left
&& mouse_y >= line_y - font_size
&& mouse_y < line_y - font_size + line_height
{
let new_cursor_line = line_i + scroll as usize;
let mut new_cursor_glyph = line.glyphs.len();
for (glyph_i, glyph) in line.glyphs.iter().enumerate() {
if mouse_x >= line_x + glyph.x as i32
&& mouse_x <= line_x + (glyph.x + glyph.w) as i32
{
new_cursor_glyph = glyph_i;
}
}
new_cursor_opt = Some(TextCursor::new(new_cursor_line, new_cursor_glyph));
}
line_y += line_height;
}
if let Some(new_cursor) = new_cursor_opt {
if new_cursor != buffer.cursor {
buffer.cursor = new_cursor;
buffer.redraw = true;
}
}
rehit = false;
let duration = instant.elapsed();
log::debug!("rehit: {:?}", duration);
}
if buffer.redraw {
let instant = Instant::now();
window.set(bg_color);
let mut line_y = line_height;
let mut start_line_opt = None;
let mut end_line = FontLineIndex::new(0);
for (line_i, line) in buffer
.layout_lines()
.iter()
.skip(scroll as usize)
.enumerate()
{
if line_y >= window.height() as i32 {
break;
}
end_line = line.line_i;
if start_line_opt == None {
start_line_opt = Some(end_line);
}
if buffer.cursor.line == line_i + scroll as usize {
if buffer.cursor.glyph >= line.glyphs.len() {
let x = match line.glyphs.last() {
Some(glyph) => glyph.x + glyph.w,
None => 0.0,
};
window.rect(
line_x + x as i32,
line_y - font_size,
(font_size / 2) as u32,
line_height as u32,
Color::rgba(0xFF, 0xFF, 0xFF, 0x20),
);
} else {
let glyph = &line.glyphs[buffer.cursor.glyph];
window.rect(
line_x + glyph.x as i32,
line_y - font_size,
glyph.w as u32,
line_height as u32,
Color::rgba(0xFF, 0xFF, 0xFF, 0x20),
);
let text_line = &buffer.text_lines()[line.line_i.get()];
log::info!(
"{}, {}: '{}' ('{}'): '{}'",
glyph.start,
glyph.end,
glyph.font.info.family,
glyph.font.info.post_script_name,
&text_line[glyph.start..glyph.end],
);
}
}
line.draw(font_color.data, |x, y, color| {
window.pixel(line_x + x, line_y + y, Color { data: color });
});
line_y += line_height;
}
// Draw scrollbar
{
let start_line = start_line_opt.unwrap_or(end_line);
let lines = buffer.text_lines().len();
let start_y = (start_line.get() * window.height() as usize) / lines;
let end_y = (end_line.get() * window.height() as usize) / lines;
if end_y > start_y {
window.rect(
window.width() as i32 - line_x as i32,
start_y as i32,
line_x as u32,
(end_y - start_y) as u32,
Color::rgba(0xFF, 0xFF, 0xFF, 0x40),
);
}
}
window.sync();
buffer.redraw = false;
let duration = instant.elapsed();
log::debug!("redraw: {:?}", duration);
}
for event in window.events() {
match event.to_option() {
EventOption::Key(event) => match event.scancode {
orbclient::K_CTRL => ctrl_pressed = event.pressed,
orbclient::K_LEFT if event.pressed => buffer.action(TextAction::Left),
orbclient::K_RIGHT if event.pressed => buffer.action(TextAction::Right),
orbclient::K_UP if event.pressed => buffer.action(TextAction::Up),
orbclient::K_DOWN if event.pressed => buffer.action(TextAction::Down),
orbclient::K_BKSP if event.pressed => buffer.action(TextAction::Backspace),
orbclient::K_DEL if event.pressed => buffer.action(TextAction::Delete),
orbclient::K_PGUP if event.pressed => {
scroll -= window_lines;
buffer.redraw = true;
},
orbclient::K_PGDN if event.pressed => {
scroll += window_lines;
buffer.redraw = true;
},
orbclient::K_0 if event.pressed && ctrl_pressed => {
font_size_i = font_size_default;
buffer.set_font_size(font_sizes[font_size_i].0 * display_scale);
},
orbclient::K_MINUS if event.pressed && ctrl_pressed => {
if font_size_i > 0 {
font_size_i -= 1;
buffer.set_font_size(font_sizes[font_size_i].0 * display_scale);
}
},
orbclient::K_EQUALS if event.pressed && ctrl_pressed => {
if font_size_i + 1 < font_sizes.len() {
font_size_i += 1;
buffer.set_font_size(font_sizes[font_size_i].0 * display_scale);
}
},
orbclient::K_D if event.pressed && ctrl_pressed => {
// Debug by shaping whole buffer
log::info!("Shaping rest of buffer");
let instant = Instant::now();
buffer.shape_until(i32::max_value());
let elapsed = instant.elapsed();
log::info!("Shaped rest of buffer in {:?}", elapsed);
}
_ => (),
},
EventOption::TextInput(event) if !ctrl_pressed => {
buffer.action(TextAction::Insert(event.character));
}
EventOption::Mouse(event) => {
mouse_x = event.x;
mouse_y = event.y;
if mouse_left {
rehit = true;
}
}
EventOption::Button(event) => {
if event.left != mouse_left {
mouse_left = event.left;
if mouse_left {
rehit = true;
}
}
}
EventOption::Resize(event) => {
buffer.set_line_width(event.width as i32 - line_x * 2);
}
EventOption::Scroll(event) => {
scroll -= event.y * 3;
buffer.redraw = true;
}
EventOption::Quit(_) => return,
_ => (),
}
}
}
}