Run cargo fmt
This commit is contained in:
parent
00bc4d1e88
commit
8cc988d374
25 changed files with 732 additions and 731 deletions
|
|
@ -1,45 +1,20 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
Element,
|
|
||||||
iced::{
|
iced::{
|
||||||
self,
|
self,
|
||||||
Color,
|
widget::{column, horizontal_space, pick_list, row},
|
||||||
Alignment,
|
Alignment, Application, Color, Command, Length,
|
||||||
Application,
|
|
||||||
Command,
|
|
||||||
Length,
|
|
||||||
widget::{
|
|
||||||
column,
|
|
||||||
horizontal_space,
|
|
||||||
pick_list,
|
|
||||||
row,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
settings,
|
settings,
|
||||||
theme::{self, Theme},
|
theme::{self, Theme},
|
||||||
widget::{
|
widget::{button, toggler},
|
||||||
button,
|
Element,
|
||||||
toggler,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
Attrs,
|
Attrs, AttrsList, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, Wrap,
|
||||||
AttrsList,
|
|
||||||
Buffer,
|
|
||||||
Edit,
|
|
||||||
FontSystem,
|
|
||||||
Metrics,
|
|
||||||
SyntaxEditor,
|
|
||||||
SyntaxSystem,
|
|
||||||
Wrap,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
env,
|
|
||||||
fs,
|
|
||||||
path::PathBuf,
|
|
||||||
sync::Mutex,
|
|
||||||
};
|
};
|
||||||
|
use std::{env, fs, path::PathBuf, sync::Mutex};
|
||||||
|
|
||||||
use self::text::text;
|
use self::text::text;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
@ -61,11 +36,7 @@ static FONT_SIZES: &'static [Metrics] = &[
|
||||||
Metrics::new(32, 44), // Title 1
|
Metrics::new(32, 44), // Title 1
|
||||||
];
|
];
|
||||||
|
|
||||||
static WRAP_MODE: &'static [Wrap] = & [
|
static WRAP_MODE: &'static [Wrap] = &[Wrap::None, Wrap::Glyph, Wrap::Word];
|
||||||
Wrap::None,
|
|
||||||
Wrap::Glyph,
|
|
||||||
Wrap::Word,
|
|
||||||
];
|
|
||||||
|
|
||||||
fn main() -> cosmic::iced::Result {
|
fn main() -> cosmic::iced::Result {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
@ -105,7 +76,7 @@ impl Window {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
log::info!("opened '{}'", path.display());
|
log::info!("opened '{}'", path.display());
|
||||||
self.path_opt = Some(path);
|
self.path_opt = Some(path);
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("failed to open '{}': {}", path.display(), err);
|
log::error!("failed to open '{}': {}", path.display(), err);
|
||||||
self.path_opt = None;
|
self.path_opt = None;
|
||||||
|
|
@ -128,8 +99,9 @@ impl Application for Window {
|
||||||
let mut editor = SyntaxEditor::new(
|
let mut editor = SyntaxEditor::new(
|
||||||
Buffer::new(&FONT_SYSTEM, FONT_SIZES[1 /* Body */]),
|
Buffer::new(&FONT_SYSTEM, FONT_SIZES[1 /* Body */]),
|
||||||
&SYNTAX_SYSTEM,
|
&SYNTAX_SYSTEM,
|
||||||
"base16-eighties.dark"
|
"base16-eighties.dark",
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
#[cfg(feature = "vi")]
|
#[cfg(feature = "vi")]
|
||||||
let mut editor = cosmic_text::ViEditor::new(editor);
|
let mut editor = cosmic_text::ViEditor::new(editor);
|
||||||
|
|
@ -154,7 +126,11 @@ impl Application for Window {
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
if let Some(path) = &self.path_opt {
|
if let Some(path) = &self.path_opt {
|
||||||
format!("COSMIC Text - {} - {}", FONT_SYSTEM.locale(), path.display())
|
format!(
|
||||||
|
"COSMIC Text - {} - {}",
|
||||||
|
FONT_SYSTEM.locale(),
|
||||||
|
path.display()
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
format!("COSMIC Text - {}", FONT_SYSTEM.locale())
|
format!("COSMIC Text - {}", FONT_SYSTEM.locale())
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +142,7 @@ impl Application for Window {
|
||||||
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
||||||
self.open(path);
|
self.open(path);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Message::Save => {
|
Message::Save => {
|
||||||
if let Some(path) = &self.path_opt {
|
if let Some(path) = &self.path_opt {
|
||||||
let editor = self.editor.lock().unwrap();
|
let editor = self.editor.lock().unwrap();
|
||||||
|
|
@ -178,13 +154,13 @@ impl Application for Window {
|
||||||
match fs::write(path, text) {
|
match fs::write(path, text) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
log::info!("saved '{}'", path.display());
|
log::info!("saved '{}'", path.display());
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("failed to save '{}': {}", path.display(), err);
|
log::error!("failed to save '{}': {}", path.display(), err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Message::Bold(bold) => {
|
Message::Bold(bold) => {
|
||||||
self.attrs = self.attrs.weight(if bold {
|
self.attrs = self.attrs.weight(if bold {
|
||||||
cosmic_text::Weight::BOLD
|
cosmic_text::Weight::BOLD
|
||||||
|
|
@ -194,7 +170,7 @@ impl Application for Window {
|
||||||
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
let mut editor = self.editor.lock().unwrap();
|
||||||
update_attrs(&mut *editor, self.attrs);
|
update_attrs(&mut *editor, self.attrs);
|
||||||
},
|
}
|
||||||
Message::Italic(italic) => {
|
Message::Italic(italic) => {
|
||||||
self.attrs = self.attrs.style(if italic {
|
self.attrs = self.attrs.style(if italic {
|
||||||
cosmic_text::Style::Italic
|
cosmic_text::Style::Italic
|
||||||
|
|
@ -204,9 +180,10 @@ impl Application for Window {
|
||||||
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
let mut editor = self.editor.lock().unwrap();
|
||||||
update_attrs(&mut *editor, self.attrs);
|
update_attrs(&mut *editor, self.attrs);
|
||||||
},
|
}
|
||||||
Message::Monospaced(monospaced) => {
|
Message::Monospaced(monospaced) => {
|
||||||
self.attrs = self.attrs
|
self.attrs = self
|
||||||
|
.attrs
|
||||||
.family(if monospaced {
|
.family(if monospaced {
|
||||||
cosmic_text::Family::Monospace
|
cosmic_text::Family::Monospace
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -216,15 +193,15 @@ impl Application for Window {
|
||||||
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
let mut editor = self.editor.lock().unwrap();
|
||||||
update_attrs(&mut *editor, self.attrs);
|
update_attrs(&mut *editor, self.attrs);
|
||||||
},
|
}
|
||||||
Message::MetricsChanged(metrics) => {
|
Message::MetricsChanged(metrics) => {
|
||||||
let mut editor = self.editor.lock().unwrap();
|
let mut editor = self.editor.lock().unwrap();
|
||||||
editor.buffer_mut().set_metrics(metrics);
|
editor.buffer_mut().set_metrics(metrics);
|
||||||
},
|
}
|
||||||
Message::WrapChanged(wrap) => {
|
Message::WrapChanged(wrap) => {
|
||||||
let mut editor = self.editor.lock().unwrap();
|
let mut editor = self.editor.lock().unwrap();
|
||||||
editor.buffer_mut().set_wrap(wrap);
|
editor.buffer_mut().set_wrap(wrap);
|
||||||
},
|
}
|
||||||
Message::ThemeChanged(theme) => {
|
Message::ThemeChanged(theme) => {
|
||||||
self.theme = match theme {
|
self.theme = match theme {
|
||||||
"Dark" => Theme::Dark,
|
"Dark" => Theme::Dark,
|
||||||
|
|
@ -234,11 +211,16 @@ impl Application for Window {
|
||||||
|
|
||||||
let Color { r, g, b, a } = self.theme.palette().text;
|
let Color { r, g, b, a } = self.theme.palette().text;
|
||||||
let as_u8 = |component: f32| (component * 255.0) as u8;
|
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)));
|
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();
|
let mut editor = self.editor.lock().unwrap();
|
||||||
update_attrs(&mut *editor, self.attrs);
|
update_attrs(&mut *editor, self.attrs);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
|
|
@ -252,7 +234,7 @@ impl Application for Window {
|
||||||
Theme::Dark => THEMES[0],
|
Theme::Dark => THEMES[0],
|
||||||
Theme::Light => THEMES[1],
|
Theme::Light => THEMES[1],
|
||||||
}),
|
}),
|
||||||
Message::ThemeChanged
|
Message::ThemeChanged,
|
||||||
);
|
);
|
||||||
|
|
||||||
let font_size_picker = {
|
let font_size_picker = {
|
||||||
|
|
@ -260,7 +242,7 @@ impl Application for Window {
|
||||||
pick_list(
|
pick_list(
|
||||||
FONT_SIZES,
|
FONT_SIZES,
|
||||||
Some(editor.buffer().metrics()),
|
Some(editor.buffer().metrics()),
|
||||||
Message::MetricsChanged
|
Message::MetricsChanged,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -283,9 +265,17 @@ impl Application for Window {
|
||||||
.on_press(Message::Save),
|
.on_press(Message::Save),
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
text("Bold:"),
|
text("Bold:"),
|
||||||
toggler(None, self.attrs.weight == cosmic_text::Weight::BOLD, Message::Bold),
|
toggler(
|
||||||
|
None,
|
||||||
|
self.attrs.weight == cosmic_text::Weight::BOLD,
|
||||||
|
Message::Bold
|
||||||
|
),
|
||||||
text("Italic:"),
|
text("Italic:"),
|
||||||
toggler(None, self.attrs.style == cosmic_text::Style::Italic, Message::Italic),
|
toggler(
|
||||||
|
None,
|
||||||
|
self.attrs.style == cosmic_text::Style::Italic,
|
||||||
|
Message::Italic
|
||||||
|
),
|
||||||
text("Monospaced:"),
|
text("Monospaced:"),
|
||||||
toggler(None, self.attrs.monospaced, Message::Monospaced),
|
toggler(None, self.attrs.monospaced, Message::Monospaced),
|
||||||
text("Theme:"),
|
text("Theme:"),
|
||||||
|
|
@ -296,8 +286,7 @@ impl Application for Window {
|
||||||
wrap_picker,
|
wrap_picker,
|
||||||
]
|
]
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.spacing(8)
|
.spacing(8),
|
||||||
,
|
|
||||||
text_box(&self.editor)
|
text_box(&self.editor)
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,16 @@
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced_native::{
|
iced_native::{
|
||||||
{Color, Element, Length, Point, Rectangle, Size},
|
|
||||||
image,
|
image,
|
||||||
layout::{self, Layout},
|
layout::{self, Layout},
|
||||||
renderer,
|
renderer,
|
||||||
widget::{self, tree, Widget},
|
widget::{self, tree, Widget},
|
||||||
|
{Color, Element, Length, Point, Rectangle, Size},
|
||||||
},
|
},
|
||||||
theme::Theme,
|
theme::Theme,
|
||||||
};
|
};
|
||||||
use cosmic_text::{
|
use cosmic_text::{Attrs, AttrsList, BufferLine, Metrics, SwashCache};
|
||||||
Attrs,
|
use std::{cmp, sync::Mutex, time::Instant};
|
||||||
AttrsList,
|
|
||||||
SwashCache,
|
|
||||||
BufferLine,
|
|
||||||
Metrics,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
cmp,
|
|
||||||
sync::Mutex,
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Appearance {
|
pub struct Appearance {
|
||||||
background_color: Option<Color>,
|
background_color: Option<Color>,
|
||||||
|
|
@ -57,10 +47,7 @@ impl Text {
|
||||||
let instant = Instant::now();
|
let instant = Instant::now();
|
||||||
|
|
||||||
//TODO: make it possible to set attrs
|
//TODO: make it possible to set attrs
|
||||||
let mut line = BufferLine::new(
|
let mut line = BufferLine::new(string, AttrsList::new(Attrs::new()));
|
||||||
string,
|
|
||||||
AttrsList::new(Attrs::new())
|
|
||||||
);
|
|
||||||
|
|
||||||
//TODO: do we have to immediately shape?
|
//TODO: do we have to immediately shape?
|
||||||
line.shape(&crate::FONT_SYSTEM);
|
line.shape(&crate::FONT_SYSTEM);
|
||||||
|
|
@ -101,11 +88,7 @@ where
|
||||||
Length::Shrink
|
Length::Shrink
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
||||||
&self,
|
|
||||||
_renderer: &Renderer,
|
|
||||||
limits: &layout::Limits,
|
|
||||||
) -> layout::Node {
|
|
||||||
let instant = Instant::now();
|
let instant = Instant::now();
|
||||||
|
|
||||||
let limits = limits.width(Length::Shrink).height(Length::Shrink);
|
let limits = limits.width(Length::Shrink).height(Length::Shrink);
|
||||||
|
|
@ -116,7 +99,7 @@ where
|
||||||
let layout_lines = shape.layout(
|
let layout_lines = shape.layout(
|
||||||
self.metrics.font_size,
|
self.metrics.font_size,
|
||||||
limits.max().width as i32,
|
limits.max().width as i32,
|
||||||
self.line.wrap()
|
self.line.wrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut width = 0;
|
let mut width = 0;
|
||||||
|
|
@ -160,7 +143,7 @@ where
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
background_color
|
background_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,11 +160,7 @@ where
|
||||||
let shape = self.line.shape_opt().as_ref().unwrap();
|
let shape = self.line.shape_opt().as_ref().unwrap();
|
||||||
|
|
||||||
//TODO: can we cache this?
|
//TODO: can we cache this?
|
||||||
let layout_lines = shape.layout(
|
let layout_lines = shape.layout(self.metrics.font_size, layout_w, self.line.wrap());
|
||||||
self.metrics.font_size,
|
|
||||||
layout_w,
|
|
||||||
self.line.wrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut cache = state.cache.lock().unwrap();
|
let mut cache = state.cache.lock().unwrap();
|
||||||
|
|
||||||
|
|
@ -219,7 +198,7 @@ pub fn draw_pixel(
|
||||||
height: i32,
|
height: i32,
|
||||||
x: i32,
|
x: i32,
|
||||||
y: i32,
|
y: i32,
|
||||||
color: cosmic_text::Color
|
color: cosmic_text::Color,
|
||||||
) {
|
) {
|
||||||
let alpha = (color.0 >> 24) & 0xFF;
|
let alpha = (color.0 >> 24) & 0xFF;
|
||||||
if alpha == 0 {
|
if alpha == 0 {
|
||||||
|
|
@ -239,11 +218,10 @@ pub fn draw_pixel(
|
||||||
|
|
||||||
let offset = (y as usize * width as usize + x as usize) * 4;
|
let offset = (y as usize * width as usize + x as usize) * 4;
|
||||||
|
|
||||||
let mut current =
|
let mut current = buffer[offset + 2] as u32
|
||||||
buffer[offset + 2] as u32 |
|
| (buffer[offset + 1] as u32) << 8
|
||||||
(buffer[offset + 1] as u32) << 8 |
|
| (buffer[offset + 0] as u32) << 16
|
||||||
(buffer[offset + 0] as u32) << 16 |
|
| (buffer[offset + 3] as u32) << 24;
|
||||||
(buffer[offset + 3] as u32) << 24;
|
|
||||||
|
|
||||||
if alpha >= 255 || current == 0 {
|
if alpha >= 255 || current == 0 {
|
||||||
// Alpha is 100% or current is null, replace with no blending
|
// Alpha is 100% or current is null, replace with no blending
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
use super::text;
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced_native::{
|
iced_native::{
|
||||||
{Color, Element, Length, Point, Rectangle, Shell, Size},
|
|
||||||
clipboard::Clipboard,
|
clipboard::Clipboard,
|
||||||
event::{Event, Status},
|
event::{Event, Status},
|
||||||
image,
|
image,
|
||||||
|
|
@ -11,21 +11,12 @@ use cosmic::{
|
||||||
mouse::{self, Button, Event as MouseEvent, ScrollDelta},
|
mouse::{self, Button, Event as MouseEvent, ScrollDelta},
|
||||||
renderer,
|
renderer,
|
||||||
widget::{self, tree, Widget},
|
widget::{self, tree, Widget},
|
||||||
Padding
|
Padding, {Color, Element, Length, Point, Rectangle, Shell, Size},
|
||||||
},
|
},
|
||||||
theme::Theme,
|
theme::Theme,
|
||||||
};
|
};
|
||||||
use cosmic_text::{
|
use cosmic_text::{Action, Edit, SwashCache};
|
||||||
Action,
|
use std::{cmp, sync::Mutex, time::Instant};
|
||||||
Edit,
|
|
||||||
SwashCache,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
cmp,
|
|
||||||
sync::Mutex,
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
use super::text;
|
|
||||||
|
|
||||||
pub struct Appearance {
|
pub struct Appearance {
|
||||||
background_color: Option<Color>,
|
background_color: Option<Color>,
|
||||||
|
|
@ -68,7 +59,6 @@ impl<'a, Editor> TextBox<'a, Editor> {
|
||||||
self.padding = padding.into();
|
self.padding = padding.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_box<'a, Editor>(editor: &'a Mutex<Editor>) -> TextBox<'a, Editor> {
|
pub fn text_box<'a, Editor>(editor: &'a Mutex<Editor>) -> TextBox<'a, Editor> {
|
||||||
|
|
@ -97,11 +87,7 @@ where
|
||||||
Length::Fill
|
Length::Fill
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
||||||
&self,
|
|
||||||
_renderer: &Renderer,
|
|
||||||
limits: &layout::Limits,
|
|
||||||
) -> layout::Node {
|
|
||||||
let limits = limits.width(Length::Fill).height(Length::Fill);
|
let limits = limits.width(Length::Fill).height(Length::Fill);
|
||||||
|
|
||||||
//TODO: allow lazy shape
|
//TODO: allow lazy shape
|
||||||
|
|
@ -160,7 +146,7 @@ where
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
background_color
|
background_color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,8 +159,10 @@ where
|
||||||
|
|
||||||
let mut editor = self.editor.lock().unwrap();
|
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_w = cmp::min(viewport.width as i32, layout.bounds().width as i32)
|
||||||
let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32) - self.padding.vertical() 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;
|
||||||
editor.buffer_mut().set_size(view_w, view_h);
|
editor.buffer_mut().set_size(view_w, view_h);
|
||||||
|
|
||||||
editor.shape_as_needed();
|
editor.shape_as_needed();
|
||||||
|
|
@ -183,41 +171,51 @@ where
|
||||||
|
|
||||||
let mut pixels = vec![0; view_w as usize * view_h as usize * 4];
|
let mut pixels = vec![0; view_w as usize * view_h as usize * 4];
|
||||||
|
|
||||||
editor.draw(&mut state.cache.lock().unwrap(), text_color, |x, y, w, h, color| {
|
editor.draw(
|
||||||
if w <= 0 || h <= 0 {
|
&mut state.cache.lock().unwrap(),
|
||||||
// Do not draw invalid sized rectangles
|
text_color,
|
||||||
return;
|
|x, y, w, h, color| {
|
||||||
}
|
if w <= 0 || h <= 0 {
|
||||||
|
// Do not draw invalid sized rectangles
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if w > 1 || h > 1 {
|
if w > 1 || h > 1 {
|
||||||
// Draw rectangles with optimized quad renderer
|
// Draw rectangles with optimized quad renderer
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle::new(
|
bounds: Rectangle::new(
|
||||||
layout.position() + [x as f32, y as f32].into() + [self.padding.left as f32, self.padding.top as f32].into(),
|
layout.position()
|
||||||
Size::new(w as f32 , h as f32)
|
+ [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: 0.0,
|
||||||
|
border_width: 0.0,
|
||||||
|
border_color: Color::TRANSPARENT,
|
||||||
|
},
|
||||||
|
Color::from_rgba8(
|
||||||
|
color.r(),
|
||||||
|
color.g(),
|
||||||
|
color.b(),
|
||||||
|
(color.a() as f32) / 255.0,
|
||||||
),
|
),
|
||||||
border_radius: 0.0,
|
);
|
||||||
border_width: 0.0,
|
} else {
|
||||||
border_color: Color::TRANSPARENT,
|
text::draw_pixel(&mut pixels, view_w, view_h, x, y, color);
|
||||||
},
|
}
|
||||||
Color::from_rgba8(
|
},
|
||||||
color.r(),
|
);
|
||||||
color.g(),
|
|
||||||
color.b(),
|
|
||||||
(color.a() as f32) / 255.0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
text::draw_pixel(&mut pixels, view_w, view_h, x, y, color);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let handle = image::Handle::from_pixels(view_w as u32, view_h as u32, pixels);
|
let handle = image::Handle::from_pixels(view_w as u32, view_h as u32, pixels);
|
||||||
image::Renderer::draw(renderer, handle, Rectangle::new(
|
image::Renderer::draw(
|
||||||
layout.position() + [self.padding.left as f32, self.padding.top as f32].into(),
|
renderer,
|
||||||
Size::new(view_w as f32, view_h as f32)
|
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();
|
let duration = instant.elapsed();
|
||||||
log::debug!("redraw {}, {}: {:?}", view_w, view_h, duration);
|
log::debug!("redraw {}, {}: {:?}", view_w, view_h, duration);
|
||||||
|
|
@ -238,101 +236,107 @@ where
|
||||||
|
|
||||||
let mut status = Status::Ignored;
|
let mut status = Status::Ignored;
|
||||||
match event {
|
match event {
|
||||||
Event::Keyboard(KeyEvent::KeyPressed { key_code, modifiers }) => match key_code {
|
Event::Keyboard(KeyEvent::KeyPressed {
|
||||||
|
key_code,
|
||||||
|
modifiers,
|
||||||
|
}) => match key_code {
|
||||||
KeyCode::Left => {
|
KeyCode::Left => {
|
||||||
editor.action(Action::Left);
|
editor.action(Action::Left);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
KeyCode::Right => {
|
KeyCode::Right => {
|
||||||
editor.action(Action::Right);
|
editor.action(Action::Right);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
editor.action(Action::Up);
|
editor.action(Action::Up);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
editor.action(Action::Down);
|
editor.action(Action::Down);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
KeyCode::Home => {
|
KeyCode::Home => {
|
||||||
editor.action(Action::Home);
|
editor.action(Action::Home);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
KeyCode::End => {
|
KeyCode::End => {
|
||||||
editor.action(Action::End);
|
editor.action(Action::End);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
KeyCode::PageUp => {
|
KeyCode::PageUp => {
|
||||||
editor.action(Action::PageUp);
|
editor.action(Action::PageUp);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
KeyCode::PageDown => {
|
KeyCode::PageDown => {
|
||||||
editor.action(Action::PageDown);
|
editor.action(Action::PageDown);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
KeyCode::Escape => {
|
KeyCode::Escape => {
|
||||||
editor.action(Action::Escape);
|
editor.action(Action::Escape);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
editor.action(Action::Enter);
|
editor.action(Action::Enter);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
KeyCode::Backspace => {
|
KeyCode::Backspace => {
|
||||||
editor.action(Action::Backspace);
|
editor.action(Action::Backspace);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
KeyCode::Delete => {
|
KeyCode::Delete => {
|
||||||
editor.action(Action::Delete);
|
editor.action(Action::Delete);
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
_ => ()
|
_ => (),
|
||||||
},
|
},
|
||||||
Event::Keyboard(KeyEvent::CharacterReceived(character)) => {
|
Event::Keyboard(KeyEvent::CharacterReceived(character)) => {
|
||||||
editor.action(Action::Insert(character));
|
editor.action(Action::Insert(character));
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
Event::Mouse(MouseEvent::ButtonPressed(Button::Left)) => {
|
Event::Mouse(MouseEvent::ButtonPressed(Button::Left)) => {
|
||||||
if layout.bounds().contains(cursor_position) {
|
if layout.bounds().contains(cursor_position) {
|
||||||
editor.action(Action::Click {
|
editor.action(Action::Click {
|
||||||
x: (cursor_position.x - layout.bounds().x) as i32 - self.padding.left as i32,
|
x: (cursor_position.x - layout.bounds().x) as i32
|
||||||
|
- self.padding.left as i32,
|
||||||
y: (cursor_position.y - layout.bounds().y) as i32 - self.padding.top as i32,
|
y: (cursor_position.y - layout.bounds().y) as i32 - self.padding.top as i32,
|
||||||
});
|
});
|
||||||
state.is_dragging = true;
|
state.is_dragging = true;
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
|
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
|
||||||
state.is_dragging = false;
|
state.is_dragging = false;
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
|
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
|
||||||
if state.is_dragging {
|
if state.is_dragging {
|
||||||
editor.action(Action::Drag {
|
editor.action(Action::Drag {
|
||||||
x: (cursor_position.x - layout.bounds().x) as i32 - self.padding.left as i32,
|
x: (cursor_position.x - layout.bounds().x) as i32
|
||||||
|
- self.padding.left as i32,
|
||||||
y: (cursor_position.y - layout.bounds().y) as i32 - self.padding.top as i32,
|
y: (cursor_position.y - layout.bounds().y) as i32 - self.padding.top as i32,
|
||||||
});
|
});
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Event::Mouse(MouseEvent::WheelScrolled { delta }) => match delta {
|
Event::Mouse(MouseEvent::WheelScrolled { delta }) => match delta {
|
||||||
ScrollDelta::Lines { x, y } => {
|
ScrollDelta::Lines { x, y } => {
|
||||||
editor.action(Action::Scroll {
|
editor.action(Action::Scroll {
|
||||||
lines: (-y * 6.0) as i32,
|
lines: (-y * 6.0) as i32,
|
||||||
});
|
});
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
},
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
_ => ()
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
status
|
status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'editor, Editor, Message, Renderer> From<TextBox<'a, Editor>> for Element<'a, Message, Renderer>
|
impl<'a, 'editor, Editor, Message, Renderer> From<TextBox<'a, Editor>>
|
||||||
|
for Element<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
|
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
|
||||||
Renderer::Theme: StyleSheet,
|
Renderer::Theme: StyleSheet,
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,14 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
Action,
|
Action, Attrs, Buffer, Edit, Family, FontSystem, Metrics, SwashCache, SyntaxEditor,
|
||||||
Attrs,
|
|
||||||
Buffer,
|
|
||||||
Edit,
|
|
||||||
Family,
|
|
||||||
FontSystem,
|
|
||||||
Metrics,
|
|
||||||
SwashCache,
|
|
||||||
SyntaxEditor,
|
|
||||||
SyntaxSystem,
|
SyntaxSystem,
|
||||||
};
|
};
|
||||||
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
||||||
use std::{env, thread, time::{Duration, Instant}};
|
use std::{
|
||||||
|
env, thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
@ -65,20 +60,18 @@ fn main() {
|
||||||
let mut editor = SyntaxEditor::new(
|
let mut editor = SyntaxEditor::new(
|
||||||
Buffer::new(&font_system, font_sizes[font_size_i]),
|
Buffer::new(&font_system, font_sizes[font_size_i]),
|
||||||
&syntax_system,
|
&syntax_system,
|
||||||
"base16-eighties.dark"
|
"base16-eighties.dark",
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
#[cfg(feature = "vi")]
|
#[cfg(feature = "vi")]
|
||||||
let mut editor = cosmic_text::ViEditor::new(editor);
|
let mut editor = cosmic_text::ViEditor::new(editor);
|
||||||
|
|
||||||
editor.buffer_mut().set_size(
|
editor
|
||||||
window.width() as i32 - line_x * 2,
|
.buffer_mut()
|
||||||
window.height() as i32
|
.set_size(window.width() as i32 - line_x * 2, window.height() as i32);
|
||||||
);
|
|
||||||
|
|
||||||
let attrs = Attrs::new()
|
let attrs = Attrs::new().monospaced(true).family(Family::Monospace);
|
||||||
.monospaced(true)
|
|
||||||
.family(Family::Monospace);
|
|
||||||
match editor.load_text(&path, attrs) {
|
match editor.load_text(&path, attrs) {
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
@ -212,7 +205,9 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventOption::Resize(event) => {
|
EventOption::Resize(event) => {
|
||||||
editor.buffer_mut().set_size(event.width as i32 - line_x * 2, event.height as i32);
|
editor
|
||||||
|
.buffer_mut()
|
||||||
|
.set_size(event.width as i32 - line_x * 2, event.height as i32);
|
||||||
}
|
}
|
||||||
EventOption::Scroll(event) => {
|
EventOption::Scroll(event) => {
|
||||||
editor.action(Action::Scroll {
|
editor.action(Action::Scroll {
|
||||||
|
|
@ -243,7 +238,7 @@ fn main() {
|
||||||
window.set_async(window_async);
|
window.set_async(window_async);
|
||||||
}
|
}
|
||||||
|
|
||||||
if window_async && ! found_event {
|
if window_async && !found_event {
|
||||||
// In async mode and no event found, sleep
|
// In async mode and no event found, sleep
|
||||||
thread::sleep(Duration::from_millis(5));
|
thread::sleep(Duration::from_millis(5));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,14 +54,8 @@ fn main() {
|
||||||
];
|
];
|
||||||
let font_size_default = 1; // Body
|
let font_size_default = 1; // Body
|
||||||
|
|
||||||
let mut buffer = Buffer::new(
|
let mut buffer = Buffer::new(&font_system, font_sizes[font_size_default]);
|
||||||
&font_system,
|
buffer.set_size(window.width() as i32, window.height() as i32);
|
||||||
font_sizes[font_size_default]
|
|
||||||
);
|
|
||||||
buffer.set_size(
|
|
||||||
window.width() as i32,
|
|
||||||
window.height() as i32
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut editor = Editor::new(buffer);
|
let mut editor = Editor::new(buffer);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,14 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
Action,
|
Action, Attrs, AttrsList, Buffer, BufferLine, Color, Edit, Editor, Family, FontSystem, Metrics,
|
||||||
Attrs,
|
Style, SwashCache, Weight,
|
||||||
AttrsList,
|
|
||||||
Buffer,
|
|
||||||
BufferLine,
|
|
||||||
Color,
|
|
||||||
Edit,
|
|
||||||
Editor,
|
|
||||||
Family,
|
|
||||||
FontSystem,
|
|
||||||
Metrics,
|
|
||||||
Style,
|
|
||||||
SwashCache,
|
|
||||||
Weight,
|
|
||||||
};
|
};
|
||||||
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
||||||
use std::{process, thread, time::{Duration, Instant}};
|
use std::{
|
||||||
|
process, thread,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
@ -47,13 +38,12 @@ fn main() {
|
||||||
|
|
||||||
let mut editor = Editor::new(Buffer::new(
|
let mut editor = Editor::new(Buffer::new(
|
||||||
&font_system,
|
&font_system,
|
||||||
Metrics::new(32, 44).scale(display_scale)
|
Metrics::new(32, 44).scale(display_scale),
|
||||||
));
|
));
|
||||||
|
|
||||||
editor.buffer_mut().set_size(
|
editor
|
||||||
window.width() as i32,
|
.buffer_mut()
|
||||||
window.height() as i32
|
.set_size(window.width() as i32, window.height() as i32);
|
||||||
);
|
|
||||||
|
|
||||||
let attrs = Attrs::new();
|
let attrs = Attrs::new();
|
||||||
let serif_attrs = attrs.family(Family::Serif);
|
let serif_attrs = attrs.family(Family::Serif);
|
||||||
|
|
@ -79,25 +69,37 @@ fn main() {
|
||||||
("Sans-Serif Normal ", attrs),
|
("Sans-Serif Normal ", attrs),
|
||||||
("Sans-Serif Bold ", attrs.weight(Weight::BOLD)),
|
("Sans-Serif Bold ", attrs.weight(Weight::BOLD)),
|
||||||
("Sans-Serif Italic ", attrs.style(Style::Italic)),
|
("Sans-Serif Italic ", attrs.style(Style::Italic)),
|
||||||
("Sans-Serif Bold Italic", attrs.weight(Weight::BOLD).style(Style::Italic)),
|
(
|
||||||
|
"Sans-Serif Bold Italic",
|
||||||
|
attrs.weight(Weight::BOLD).style(Style::Italic),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
&[
|
&[
|
||||||
("Serif Normal ", serif_attrs),
|
("Serif Normal ", serif_attrs),
|
||||||
("Serif Bold ", serif_attrs.weight(Weight::BOLD)),
|
("Serif Bold ", serif_attrs.weight(Weight::BOLD)),
|
||||||
("Serif Italic ", serif_attrs.style(Style::Italic)),
|
("Serif Italic ", serif_attrs.style(Style::Italic)),
|
||||||
("Serif Bold Italic", serif_attrs.weight(Weight::BOLD).style(Style::Italic)),
|
(
|
||||||
|
"Serif Bold Italic",
|
||||||
|
serif_attrs.weight(Weight::BOLD).style(Style::Italic),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
&[
|
&[
|
||||||
("Mono Normal ", mono_attrs),
|
("Mono Normal ", mono_attrs),
|
||||||
("Mono Bold ", mono_attrs.weight(Weight::BOLD)),
|
("Mono Bold ", mono_attrs.weight(Weight::BOLD)),
|
||||||
("Mono Italic ", mono_attrs.style(Style::Italic)),
|
("Mono Italic ", mono_attrs.style(Style::Italic)),
|
||||||
("Mono Bold Italic", mono_attrs.weight(Weight::BOLD).style(Style::Italic)),
|
(
|
||||||
|
"Mono Bold Italic",
|
||||||
|
mono_attrs.weight(Weight::BOLD).style(Style::Italic),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
&[
|
&[
|
||||||
("Comic Normal ", comic_attrs),
|
("Comic Normal ", comic_attrs),
|
||||||
("Comic Bold ", comic_attrs.weight(Weight::BOLD)),
|
("Comic Bold ", comic_attrs.weight(Weight::BOLD)),
|
||||||
("Comic Italic ", comic_attrs.style(Style::Italic)),
|
("Comic Italic ", comic_attrs.style(Style::Italic)),
|
||||||
("Comic Bold Italic", comic_attrs.weight(Weight::BOLD).style(Style::Italic)),
|
(
|
||||||
|
"Comic Bold Italic",
|
||||||
|
comic_attrs.weight(Weight::BOLD).style(Style::Italic),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
&[
|
&[
|
||||||
("R", attrs.color(Color::rgb(0xFF, 0x00, 0x00))),
|
("R", attrs.color(Color::rgb(0xFF, 0x00, 0x00))),
|
||||||
|
|
@ -122,9 +124,10 @@ fn main() {
|
||||||
("R", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))),
|
("R", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))),
|
||||||
("N", attrs.color(Color::rgb(0xFF, 0x00, 0x00))),
|
("N", attrs.color(Color::rgb(0xFF, 0x00, 0x00))),
|
||||||
],
|
],
|
||||||
&[
|
&[(
|
||||||
("生活,삶,जिंदगी 😀 FPS", attrs.color(Color::rgb(0xFF, 0x00, 0x00)))
|
"生活,삶,जिंदगी 😀 FPS",
|
||||||
]
|
attrs.color(Color::rgb(0xFF, 0x00, 0x00)),
|
||||||
|
)],
|
||||||
];
|
];
|
||||||
for &line in lines {
|
for &line in lines {
|
||||||
let mut line_text = String::new();
|
let mut line_text = String::new();
|
||||||
|
|
@ -135,7 +138,10 @@ fn main() {
|
||||||
let end = line_text.len();
|
let end = line_text.len();
|
||||||
attrs_list.add_span(start..end, attrs);
|
attrs_list.add_span(start..end, attrs);
|
||||||
}
|
}
|
||||||
editor.buffer_mut().lines.push(BufferLine::new(line_text, attrs_list));
|
editor
|
||||||
|
.buffer_mut()
|
||||||
|
.lines
|
||||||
|
.push(BufferLine::new(line_text, attrs_list));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut swash_cache = SwashCache::new(&font_system);
|
let mut swash_cache = SwashCache::new(&font_system);
|
||||||
|
|
@ -187,18 +193,26 @@ fn main() {
|
||||||
mouse_x = mouse.x;
|
mouse_x = mouse.x;
|
||||||
mouse_y = mouse.y;
|
mouse_y = mouse.y;
|
||||||
if mouse_left {
|
if mouse_left {
|
||||||
editor.action(Action::Drag { x: mouse_x, y: mouse_y });
|
editor.action(Action::Drag {
|
||||||
|
x: mouse_x,
|
||||||
|
y: mouse_y,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
EventOption::Button(button) => {
|
EventOption::Button(button) => {
|
||||||
mouse_left = button.left;
|
mouse_left = button.left;
|
||||||
if mouse_left {
|
if mouse_left {
|
||||||
editor.action(Action::Click { x: mouse_x, y: mouse_y });
|
editor.action(Action::Click {
|
||||||
|
x: mouse_x,
|
||||||
|
y: mouse_y,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
EventOption::Resize(resize) => {
|
EventOption::Resize(resize) => {
|
||||||
editor.buffer_mut().set_size(resize.width as i32, resize.height as i32);
|
editor
|
||||||
},
|
.buffer_mut()
|
||||||
|
.set_size(resize.width as i32, resize.height as i32);
|
||||||
|
}
|
||||||
EventOption::Quit(_) => process::exit(0),
|
EventOption::Quit(_) => process::exit(0),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics};
|
use cosmic_text::{Attrs, Buffer, Color, FontSystem, Metrics, SwashCache};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use termion::{
|
use termion::{color, cursor};
|
||||||
color,
|
|
||||||
cursor,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// A FontSystem provides access to detected system fonts, create one per application
|
// A FontSystem provides access to detected system fonts, create one per application
|
||||||
|
|
@ -66,11 +63,7 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale by alpha (mimics blending with black)
|
// Scale by alpha (mimics blending with black)
|
||||||
let scale = |c: u8| {
|
let scale = |c: u8| cmp::max(0, cmp::min(255, ((c as i32) * (a as i32)) / 255)) as u8;
|
||||||
cmp::max(0, cmp::min(255,
|
|
||||||
((c as i32) * (a as i32)) / 255
|
|
||||||
)) as u8
|
|
||||||
};
|
|
||||||
|
|
||||||
// Navigate to x coordinate
|
// Navigate to x coordinate
|
||||||
if x > last_x {
|
if x > last_x {
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
disable_all_formatting = true
|
|
||||||
49
src/attrs.rs
49
src/attrs.rs
|
|
@ -24,12 +24,7 @@ impl Color {
|
||||||
/// Create new color with red, green, blue, and alpha components
|
/// Create new color with red, green, blue, and alpha components
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
|
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
Self(
|
Self(((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
|
||||||
((a as u32) << 24) |
|
|
||||||
((r as u32) << 16) |
|
|
||||||
((g as u32) << 8) |
|
|
||||||
(b as u32)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the red component
|
/// Get the red component
|
||||||
|
|
@ -165,22 +160,20 @@ impl<'a> Attrs<'a> {
|
||||||
/// Check if font matches
|
/// Check if font matches
|
||||||
pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
|
pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
|
||||||
//TODO: smarter way of including emoji
|
//TODO: smarter way of including emoji
|
||||||
face.post_script_name.contains("Emoji") ||
|
face.post_script_name.contains("Emoji")
|
||||||
(
|
|| (face.style == self.style
|
||||||
face.style == self.style &&
|
&& face.weight == self.weight
|
||||||
face.weight == self.weight &&
|
&& face.stretch == self.stretch
|
||||||
face.stretch == self.stretch &&
|
&& face.monospaced == self.monospaced)
|
||||||
face.monospaced == self.monospaced
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if this set of attributes can be shaped with another
|
/// Check if this set of attributes can be shaped with another
|
||||||
pub fn compatible(&self, other: &Self) -> bool {
|
pub fn compatible(&self, other: &Self) -> bool {
|
||||||
self.family == other.family
|
self.family == other.family
|
||||||
&& self.monospaced == other.monospaced
|
&& self.monospaced == other.monospaced
|
||||||
&& self.stretch == other.stretch
|
&& self.stretch == other.stretch
|
||||||
&& self.style == other.style
|
&& self.style == other.style
|
||||||
&& self.weight == other.weight
|
&& self.weight == other.weight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,7 +261,10 @@ impl AttrsList {
|
||||||
///
|
///
|
||||||
/// This returns a span that contains the index
|
/// This returns a span that contains the index
|
||||||
pub fn get_span(&self, index: usize) -> Attrs {
|
pub fn get_span(&self, index: usize) -> Attrs {
|
||||||
self.spans.get(&index).map(|v| v.as_attrs()).unwrap_or(self.defaults.as_attrs())
|
self.spans
|
||||||
|
.get(&index)
|
||||||
|
.map(|v| v.as_attrs())
|
||||||
|
.unwrap_or(self.defaults.as_attrs())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Split attributes list at an offset
|
/// Split attributes list at an offset
|
||||||
|
|
@ -288,20 +284,19 @@ impl AttrsList {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (key, resize) in removes {
|
for (key, resize) in removes {
|
||||||
let (range, attrs) = self.spans.get_key_value(&key.start).map(|v| (v.0.clone(), v.1.clone())).expect("attrs span not found");
|
let (range, attrs) = self
|
||||||
|
.spans
|
||||||
|
.get_key_value(&key.start)
|
||||||
|
.map(|v| (v.0.clone(), v.1.clone()))
|
||||||
|
.expect("attrs span not found");
|
||||||
self.spans.remove(key);
|
self.spans.remove(key);
|
||||||
|
|
||||||
if resize {
|
if resize {
|
||||||
new.spans.insert(
|
new.spans.insert(0..range.end - index, attrs.clone());
|
||||||
0..range.end - index,
|
|
||||||
attrs.clone()
|
|
||||||
);
|
|
||||||
self.spans.insert(range.start..index, attrs);
|
self.spans.insert(range.start..index, attrs);
|
||||||
} else {
|
} else {
|
||||||
new.spans.insert(
|
new.spans
|
||||||
range.start - index..range.end - index,
|
.insert(range.start - index..range.end - index, attrs);
|
||||||
attrs
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new
|
new
|
||||||
|
|
|
||||||
153
src/buffer.rs
153
src/buffer.rs
|
|
@ -5,15 +5,12 @@ use alloc::{
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
use core::{
|
use core::{cmp, fmt};
|
||||||
cmp,
|
|
||||||
fmt,
|
|
||||||
};
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap};
|
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
use crate::Color;
|
use crate::Color;
|
||||||
|
use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap};
|
||||||
|
|
||||||
/// Current cursor location
|
/// Current cursor location
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
|
@ -39,7 +36,11 @@ pub struct LayoutCursor {
|
||||||
|
|
||||||
impl LayoutCursor {
|
impl LayoutCursor {
|
||||||
pub fn new(line: usize, layout: usize, glyph: usize) -> Self {
|
pub fn new(line: usize, layout: usize, glyph: usize) -> Self {
|
||||||
Self { line, layout, glyph }
|
Self {
|
||||||
|
line,
|
||||||
|
layout,
|
||||||
|
glyph,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,13 +82,25 @@ impl<'a> LayoutRun<'a> {
|
||||||
let cursor = Cursor::new(self.line_i, self.glyphs.last().map_or(0, |glyph| glyph.end));
|
let cursor = Cursor::new(self.line_i, self.glyphs.last().map_or(0, |glyph| glyph.end));
|
||||||
if cursor >= cursor_start && cursor <= cursor_end {
|
if cursor >= cursor_start && cursor <= cursor_end {
|
||||||
if x_start.is_none() {
|
if x_start.is_none() {
|
||||||
x_start = Some(self.glyphs.last().map_or(0., |glyph| glyph.x + glyph.w * ltr_factor));
|
x_start = Some(
|
||||||
|
self.glyphs
|
||||||
|
.last()
|
||||||
|
.map_or(0., |glyph| glyph.x + glyph.w * ltr_factor),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
x_end = Some(self.glyphs.last().map_or(0., |glyph| glyph.x + glyph.w * ltr_factor));
|
x_end = Some(
|
||||||
|
self.glyphs
|
||||||
|
.last()
|
||||||
|
.map_or(0., |glyph| glyph.x + glyph.w * ltr_factor),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if let Some(x_start) = x_start {
|
if let Some(x_start) = x_start {
|
||||||
let x_end = x_end.expect("end of cursor not found");
|
let x_end = x_end.expect("end of cursor not found");
|
||||||
let (x_start, x_end) = if x_start < x_end { (x_start, x_end) } else { (x_end, x_start) };
|
let (x_start, x_end) = if x_start < x_end {
|
||||||
|
(x_start, x_end)
|
||||||
|
} else {
|
||||||
|
(x_end, x_start)
|
||||||
|
};
|
||||||
Some((x_start, x_end - x_start))
|
Some((x_start, x_end - x_start))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -107,14 +120,28 @@ pub struct LayoutRunIter<'a, 'b> {
|
||||||
|
|
||||||
impl<'a, 'b> LayoutRunIter<'a, 'b> {
|
impl<'a, 'b> LayoutRunIter<'a, 'b> {
|
||||||
pub fn new(buffer: &'b Buffer<'a>) -> Self {
|
pub fn new(buffer: &'b Buffer<'a>) -> Self {
|
||||||
let total_layout_lines: usize = buffer.lines.iter().map(|line| line.layout_opt().as_ref().map(|layout| layout.len()).unwrap_or_default()).sum();
|
let total_layout_lines: usize = buffer
|
||||||
let top_cropped_layout_lines = total_layout_lines.saturating_sub(buffer.scroll.try_into().unwrap_or_default());
|
.lines
|
||||||
let maximum_lines = buffer.height.checked_div(buffer.metrics.line_height).unwrap_or_default();
|
.iter()
|
||||||
let bottom_cropped_layout_lines = if top_cropped_layout_lines > maximum_lines.try_into().unwrap_or_default() {
|
.map(|line| {
|
||||||
maximum_lines.try_into().unwrap_or_default()
|
line.layout_opt()
|
||||||
} else {
|
.as_ref()
|
||||||
top_cropped_layout_lines
|
.map(|layout| layout.len())
|
||||||
};
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
let top_cropped_layout_lines =
|
||||||
|
total_layout_lines.saturating_sub(buffer.scroll.try_into().unwrap_or_default());
|
||||||
|
let maximum_lines = buffer
|
||||||
|
.height
|
||||||
|
.checked_div(buffer.metrics.line_height)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let bottom_cropped_layout_lines =
|
||||||
|
if top_cropped_layout_lines > maximum_lines.try_into().unwrap_or_default() {
|
||||||
|
maximum_lines.try_into().unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
top_cropped_layout_lines
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
buffer,
|
buffer,
|
||||||
line_i: 0,
|
line_i: 0,
|
||||||
|
|
@ -169,7 +196,7 @@ impl<'a, 'b> Iterator for LayoutRunIter<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> ExactSizeIterator for LayoutRunIter<'a, 'b> { }
|
impl<'a, 'b> ExactSizeIterator for LayoutRunIter<'a, 'b> {}
|
||||||
|
|
||||||
/// Metrics of text
|
/// Metrics of text
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
|
@ -182,7 +209,10 @@ pub struct Metrics {
|
||||||
|
|
||||||
impl Metrics {
|
impl Metrics {
|
||||||
pub const fn new(font_size: i32, line_height: i32) -> Self {
|
pub const fn new(font_size: i32, line_height: i32) -> Self {
|
||||||
Self { font_size, line_height }
|
Self {
|
||||||
|
font_size,
|
||||||
|
line_height,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn scale(self, scale: i32) -> Self {
|
pub const fn scale(self, scale: i32) -> Self {
|
||||||
|
|
@ -215,10 +245,7 @@ pub struct Buffer<'a> {
|
||||||
|
|
||||||
impl<'a> Buffer<'a> {
|
impl<'a> Buffer<'a> {
|
||||||
/// Create a new [`Buffer`] with the provided [`FontSystem`] and [`Metrics`]
|
/// Create a new [`Buffer`] with the provided [`FontSystem`] and [`Metrics`]
|
||||||
pub fn new(
|
pub fn new(font_system: &'a FontSystem, metrics: Metrics) -> Self {
|
||||||
font_system: &'a FontSystem,
|
|
||||||
metrics: Metrics,
|
|
||||||
) -> Self {
|
|
||||||
let mut buffer = Self {
|
let mut buffer = Self {
|
||||||
font_system,
|
font_system,
|
||||||
lines: Vec::new(),
|
lines: Vec::new(),
|
||||||
|
|
@ -244,7 +271,7 @@ impl<'a> Buffer<'a> {
|
||||||
self.font_system,
|
self.font_system,
|
||||||
self.metrics.font_size,
|
self.metrics.font_size,
|
||||||
self.width,
|
self.width,
|
||||||
self.wrap
|
self.wrap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -274,7 +301,7 @@ impl<'a> Buffer<'a> {
|
||||||
self.font_system,
|
self.font_system,
|
||||||
self.metrics.font_size,
|
self.metrics.font_size,
|
||||||
self.width,
|
self.width,
|
||||||
self.wrap
|
self.wrap,
|
||||||
);
|
);
|
||||||
total_layout += layout.len() as i32;
|
total_layout += layout.len() as i32;
|
||||||
}
|
}
|
||||||
|
|
@ -307,7 +334,7 @@ impl<'a> Buffer<'a> {
|
||||||
self.font_system,
|
self.font_system,
|
||||||
self.metrics.font_size,
|
self.metrics.font_size,
|
||||||
self.width,
|
self.width,
|
||||||
self.wrap
|
self.wrap,
|
||||||
);
|
);
|
||||||
if line_i == cursor.line {
|
if line_i == cursor.line {
|
||||||
let layout_cursor = self.layout_cursor(&cursor);
|
let layout_cursor = self.layout_cursor(&cursor);
|
||||||
|
|
@ -341,13 +368,7 @@ impl<'a> Buffer<'a> {
|
||||||
let scroll_end = self.scroll + lines;
|
let scroll_end = self.scroll + lines;
|
||||||
let total_layout = self.shape_until(scroll_end);
|
let total_layout = self.shape_until(scroll_end);
|
||||||
|
|
||||||
self.scroll = cmp::max(
|
self.scroll = cmp::max(0, cmp::min(total_layout - (lines - 1), self.scroll));
|
||||||
0,
|
|
||||||
cmp::min(
|
|
||||||
total_layout - (lines - 1),
|
|
||||||
self.scroll,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layout_cursor(&self, cursor: &Cursor) -> LayoutCursor {
|
pub fn layout_cursor(&self, cursor: &Cursor) -> LayoutCursor {
|
||||||
|
|
@ -358,40 +379,24 @@ impl<'a> Buffer<'a> {
|
||||||
for (layout_i, layout_line) in layout.iter().enumerate() {
|
for (layout_i, layout_line) in layout.iter().enumerate() {
|
||||||
for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
|
for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
|
||||||
if cursor.index == glyph.start {
|
if cursor.index == glyph.start {
|
||||||
return LayoutCursor::new(
|
return LayoutCursor::new(cursor.line, layout_i, glyph_i);
|
||||||
cursor.line,
|
|
||||||
layout_i,
|
|
||||||
glyph_i
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match layout_line.glyphs.last() {
|
match layout_line.glyphs.last() {
|
||||||
Some(glyph) => {
|
Some(glyph) => {
|
||||||
if cursor.index == glyph.end {
|
if cursor.index == glyph.end {
|
||||||
return LayoutCursor::new(
|
return LayoutCursor::new(cursor.line, layout_i, layout_line.glyphs.len());
|
||||||
cursor.line,
|
|
||||||
layout_i,
|
|
||||||
layout_line.glyphs.len()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
return LayoutCursor::new(
|
return LayoutCursor::new(cursor.line, layout_i, 0);
|
||||||
cursor.line,
|
|
||||||
layout_i,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to start of line
|
// Fall back to start of line
|
||||||
//TODO: should this be the end of the line?
|
//TODO: should this be the end of the line?
|
||||||
LayoutCursor::new(
|
LayoutCursor::new(cursor.line, 0, 0)
|
||||||
cursor.line,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get [`FontSystem`] used by this [`Buffer`]
|
/// Get [`FontSystem`] used by this [`Buffer`]
|
||||||
|
|
@ -408,7 +413,12 @@ impl<'a> Buffer<'a> {
|
||||||
/// Lay out the provided line index and return the result
|
/// Lay out the provided line index and return the result
|
||||||
pub fn line_layout(&mut self, line_i: usize) -> Option<&[LayoutLine]> {
|
pub fn line_layout(&mut self, line_i: usize) -> Option<&[LayoutLine]> {
|
||||||
let line = self.lines.get_mut(line_i)?;
|
let line = self.lines.get_mut(line_i)?;
|
||||||
Some(line.layout(self.font_system, self.metrics.font_size, self.width, self.wrap))
|
Some(line.layout(
|
||||||
|
self.font_system,
|
||||||
|
self.metrics.font_size,
|
||||||
|
self.width,
|
||||||
|
self.wrap,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current [`Metrics`]
|
/// Get the current [`Metrics`]
|
||||||
|
|
@ -476,11 +486,13 @@ impl<'a> Buffer<'a> {
|
||||||
pub fn set_text(&mut self, text: &str, attrs: Attrs<'a>) {
|
pub fn set_text(&mut self, text: &str, attrs: Attrs<'a>) {
|
||||||
self.lines.clear();
|
self.lines.clear();
|
||||||
for line in text.lines() {
|
for line in text.lines() {
|
||||||
self.lines.push(BufferLine::new(line.to_string(), AttrsList::new(attrs)));
|
self.lines
|
||||||
|
.push(BufferLine::new(line.to_string(), AttrsList::new(attrs)));
|
||||||
}
|
}
|
||||||
// Make sure there is always one line
|
// Make sure there is always one line
|
||||||
if self.lines.is_empty() {
|
if self.lines.is_empty() {
|
||||||
self.lines.push(BufferLine::new(String::new(), AttrsList::new(attrs)));
|
self.lines
|
||||||
|
.push(BufferLine::new(String::new(), AttrsList::new(attrs)));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.scroll = 0;
|
self.scroll = 0;
|
||||||
|
|
@ -522,9 +534,7 @@ impl<'a> Buffer<'a> {
|
||||||
first_run = false;
|
first_run = false;
|
||||||
let new_cursor = Cursor::new(run.line_i, 0);
|
let new_cursor = Cursor::new(run.line_i, 0);
|
||||||
new_cursor_opt = Some(new_cursor);
|
new_cursor_opt = Some(new_cursor);
|
||||||
} else if y >= line_y - font_size
|
} else if y >= line_y - font_size && y < line_y - font_size + line_height {
|
||||||
&& y < line_y - font_size + line_height
|
|
||||||
{
|
|
||||||
let mut new_cursor_glyph = run.glyphs.len();
|
let mut new_cursor_glyph = run.glyphs.len();
|
||||||
let mut new_cursor_char = 0;
|
let mut new_cursor_char = 0;
|
||||||
|
|
||||||
|
|
@ -538,9 +548,7 @@ impl<'a> Buffer<'a> {
|
||||||
new_cursor_char = 0;
|
new_cursor_char = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if x >= glyph.x as i32
|
if x >= glyph.x as i32 && x <= (glyph.x + glyph.w) as i32 {
|
||||||
&& x <= (glyph.x + glyph.w) as i32
|
|
||||||
{
|
|
||||||
new_cursor_glyph = glyph_i;
|
new_cursor_glyph = glyph_i;
|
||||||
|
|
||||||
let cluster = &run.text[glyph.start..glyph.end];
|
let cluster = &run.text[glyph.start..glyph.end];
|
||||||
|
|
@ -548,9 +556,7 @@ impl<'a> Buffer<'a> {
|
||||||
let mut egc_x = glyph.x;
|
let mut egc_x = glyph.x;
|
||||||
let egc_w = glyph.w / (total as f32);
|
let egc_w = glyph.w / (total as f32);
|
||||||
for (egc_i, egc) in cluster.grapheme_indices(true) {
|
for (egc_i, egc) in cluster.grapheme_indices(true) {
|
||||||
if x >= egc_x as i32
|
if x >= egc_x as i32 && x <= (egc_x + egc_w) as i32 {
|
||||||
&& x <= (egc_x + egc_w) as i32
|
|
||||||
{
|
|
||||||
new_cursor_char = egc_i;
|
new_cursor_char = egc_i;
|
||||||
|
|
||||||
let right_half = x >= (egc_x + egc_w / 2.0) as i32;
|
let right_half = x >= (egc_x + egc_w / 2.0) as i32;
|
||||||
|
|
@ -578,11 +584,13 @@ impl<'a> Buffer<'a> {
|
||||||
Some(glyph) => {
|
Some(glyph) => {
|
||||||
// Position at glyph
|
// Position at glyph
|
||||||
new_cursor.index = glyph.start + new_cursor_char;
|
new_cursor.index = glyph.start + new_cursor_char;
|
||||||
},
|
}
|
||||||
None => if let Some(glyph) = run.glyphs.last() {
|
None => {
|
||||||
// Position at end of line
|
if let Some(glyph) = run.glyphs.last() {
|
||||||
new_cursor.index = glyph.end;
|
// Position at end of line
|
||||||
},
|
new_cursor.index = glyph.end;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new_cursor_opt = Some(new_cursor);
|
new_cursor_opt = Some(new_cursor);
|
||||||
|
|
@ -606,7 +614,8 @@ impl<'a> Buffer<'a> {
|
||||||
/// Draw the buffer
|
/// Draw the buffer
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
pub fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
|
pub fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
|
||||||
where F: FnMut(i32, i32, u32, u32, Color)
|
where
|
||||||
|
F: FnMut(i32, i32, u32, u32, Color),
|
||||||
{
|
{
|
||||||
for run in self.layout_runs() {
|
for run in self.layout_runs() {
|
||||||
for glyph in run.glyphs.iter() {
|
for glyph in run.glyphs.iter() {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::{
|
use alloc::{string::String, vec::Vec};
|
||||||
string::String,
|
|
||||||
vec::Vec,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{AttrsList, FontSystem, LayoutLine, ShapeLine, Wrap};
|
use crate::{AttrsList, FontSystem, LayoutLine, ShapeLine, Wrap};
|
||||||
|
|
||||||
|
|
@ -39,7 +36,11 @@ impl BufferLine {
|
||||||
///
|
///
|
||||||
/// Will reset shape and layout if it differs from current text and attributes list.
|
/// Will reset shape and layout if it differs from current text and attributes list.
|
||||||
/// Returns true if the line was reset
|
/// Returns true if the line was reset
|
||||||
pub fn set_text<T: AsRef<str> + Into<String>>(&mut self, text: T, attrs_list: AttrsList) -> bool {
|
pub fn set_text<T: AsRef<str> + Into<String>>(
|
||||||
|
&mut self,
|
||||||
|
text: T,
|
||||||
|
attrs_list: AttrsList,
|
||||||
|
) -> bool {
|
||||||
if text.as_ref() != self.text || attrs_list != self.attrs_list {
|
if text.as_ref() != self.text || attrs_list != self.attrs_list {
|
||||||
self.text = text.into();
|
self.text = text.into();
|
||||||
self.attrs_list = attrs_list;
|
self.attrs_list = attrs_list;
|
||||||
|
|
@ -102,7 +103,8 @@ impl BufferLine {
|
||||||
|
|
||||||
if other.attrs_list.defaults() != self.attrs_list.defaults() {
|
if other.attrs_list.defaults() != self.attrs_list.defaults() {
|
||||||
// If default formatting does not match, make a new span for it
|
// If default formatting does not match, make a new span for it
|
||||||
self.attrs_list.add_span(len..len + other.text().len(), other.attrs_list.defaults());
|
self.attrs_list
|
||||||
|
.add_span(len..len + other.text().len(), other.attrs_list.defaults());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (other_range, attrs) in other.attrs_list.spans() {
|
for (other_range, attrs) in other.attrs_list.spans() {
|
||||||
|
|
@ -157,15 +159,17 @@ impl BufferLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout line, will cache results
|
/// Layout line, will cache results
|
||||||
pub fn layout(&mut self, font_system: &FontSystem, font_size: i32, width: i32, wrap: Wrap) -> &[LayoutLine] {
|
pub fn layout(
|
||||||
|
&mut self,
|
||||||
|
font_system: &FontSystem,
|
||||||
|
font_size: i32,
|
||||||
|
width: i32,
|
||||||
|
wrap: Wrap,
|
||||||
|
) -> &[LayoutLine] {
|
||||||
if self.layout_opt.is_none() {
|
if self.layout_opt.is_none() {
|
||||||
self.wrap = wrap;
|
self.wrap = wrap;
|
||||||
let shape = self.shape(font_system);
|
let shape = self.shape(font_system);
|
||||||
let layout = shape.layout(
|
let layout = shape.layout(font_size, width, wrap);
|
||||||
font_size,
|
|
||||||
width,
|
|
||||||
wrap
|
|
||||||
);
|
|
||||||
self.layout_opt = Some(layout);
|
self.layout_opt = Some(layout);
|
||||||
}
|
}
|
||||||
self.layout_opt.as_ref().expect("layout not found")
|
self.layout_opt.as_ref().expect("layout not found")
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ use alloc::string::String;
|
||||||
use core::{cmp, iter::once};
|
use core::{cmp, iter::once};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::{Action, AttrsList, Buffer, BufferLine, Cursor, Edit, LayoutCursor};
|
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
use crate::Color;
|
use crate::Color;
|
||||||
|
use crate::{Action, AttrsList, Buffer, BufferLine, Cursor, Edit, LayoutCursor};
|
||||||
|
|
||||||
/// A wrapper of [`Buffer`] for easy editing
|
/// A wrapper of [`Buffer`] for easy editing
|
||||||
pub struct Editor<'a> {
|
pub struct Editor<'a> {
|
||||||
|
|
@ -31,14 +31,17 @@ impl<'a> Editor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_layout_cursor(&mut self, cursor: LayoutCursor) {
|
fn set_layout_cursor(&mut self, cursor: LayoutCursor) {
|
||||||
let layout = self.buffer.line_layout(cursor.line).expect("layout not found");
|
let layout = self
|
||||||
|
.buffer
|
||||||
|
.line_layout(cursor.line)
|
||||||
|
.expect("layout not found");
|
||||||
|
|
||||||
let layout_line = match layout.get(cursor.layout) {
|
let layout_line = match layout.get(cursor.layout) {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => match layout.last() {
|
None => match layout.last() {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => todo!("layout cursor in line with no layouts"),
|
None => todo!("layout cursor in line with no layouts"),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_index = match layout_line.glyphs.get(cursor.glyph) {
|
let new_index = match layout_line.glyphs.get(cursor.glyph) {
|
||||||
|
|
@ -47,7 +50,7 @@ impl<'a> Editor<'a> {
|
||||||
Some(glyph) => glyph.end,
|
Some(glyph) => glyph.end,
|
||||||
//TODO: is this correct?
|
//TODO: is this correct?
|
||||||
None => 0,
|
None => 0,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.cursor.line != cursor.line || self.cursor.index != new_index {
|
if self.cursor.line != cursor.line || self.cursor.index != new_index {
|
||||||
|
|
@ -217,7 +220,8 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
let after_len = after.text().len();
|
let after_len = after.text().len();
|
||||||
|
|
||||||
// Collect attributes
|
// Collect attributes
|
||||||
let mut final_attrs = attrs_list.unwrap_or_else(|| AttrsList::new(line.attrs_list().get_span(line.text().len())));
|
let mut final_attrs = attrs_list
|
||||||
|
.unwrap_or_else(|| AttrsList::new(line.attrs_list().get_span(line.text().len())));
|
||||||
|
|
||||||
// Append the inserted text, line by line
|
// Append the inserted text, line by line
|
||||||
// we want to see a blank entry if the string ends with a newline
|
// we want to see a blank entry if the string ends with a newline
|
||||||
|
|
@ -227,13 +231,23 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
let mut these_attrs = final_attrs.split_off(data_line.len());
|
let mut these_attrs = final_attrs.split_off(data_line.len());
|
||||||
remaining_split_len -= data_line.len();
|
remaining_split_len -= data_line.len();
|
||||||
core::mem::swap(&mut these_attrs, &mut final_attrs);
|
core::mem::swap(&mut these_attrs, &mut final_attrs);
|
||||||
line.append(BufferLine::new(data_line.strip_suffix(char::is_control).unwrap_or(data_line), these_attrs));
|
line.append(BufferLine::new(
|
||||||
|
data_line
|
||||||
|
.strip_suffix(char::is_control)
|
||||||
|
.unwrap_or(data_line),
|
||||||
|
these_attrs,
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
panic!("str::lines() did not yield any elements");
|
panic!("str::lines() did not yield any elements");
|
||||||
}
|
}
|
||||||
if let Some(data_line) = lines_iter.next_back() {
|
if let Some(data_line) = lines_iter.next_back() {
|
||||||
remaining_split_len -= data_line.len();
|
remaining_split_len -= data_line.len();
|
||||||
let mut tmp = BufferLine::new(data_line.strip_suffix(char::is_control).unwrap_or(data_line), final_attrs.split_off(remaining_split_len));
|
let mut tmp = BufferLine::new(
|
||||||
|
data_line
|
||||||
|
.strip_suffix(char::is_control)
|
||||||
|
.unwrap_or(data_line),
|
||||||
|
final_attrs.split_off(remaining_split_len),
|
||||||
|
);
|
||||||
tmp.append(after);
|
tmp.append(after);
|
||||||
self.buffer.lines.insert(insert_line, tmp);
|
self.buffer.lines.insert(insert_line, tmp);
|
||||||
self.cursor.line += 1;
|
self.cursor.line += 1;
|
||||||
|
|
@ -242,7 +256,12 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
}
|
}
|
||||||
for data_line in lines_iter.rev() {
|
for data_line in lines_iter.rev() {
|
||||||
remaining_split_len -= data_line.len();
|
remaining_split_len -= data_line.len();
|
||||||
let tmp = BufferLine::new(data_line.strip_suffix(char::is_control).unwrap_or(data_line), final_attrs.split_off(remaining_split_len));
|
let tmp = BufferLine::new(
|
||||||
|
data_line
|
||||||
|
.strip_suffix(char::is_control)
|
||||||
|
.unwrap_or(data_line),
|
||||||
|
final_attrs.split_off(remaining_split_len),
|
||||||
|
);
|
||||||
self.buffer.lines.insert(insert_line, tmp);
|
self.buffer.lines.insert(insert_line, tmp);
|
||||||
self.cursor.line += 1;
|
self.cursor.line += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -278,7 +297,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
self.buffer.set_redraw(true);
|
self.buffer.set_redraw(true);
|
||||||
}
|
}
|
||||||
self.cursor_x_opt = None;
|
self.cursor_x_opt = None;
|
||||||
},
|
}
|
||||||
Action::Next => {
|
Action::Next => {
|
||||||
let line = &mut self.buffer.lines[self.cursor.line];
|
let line = &mut self.buffer.lines[self.cursor.line];
|
||||||
if self.cursor.index < line.text().len() {
|
if self.cursor.index < line.text().len() {
|
||||||
|
|
@ -295,9 +314,12 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
self.buffer.set_redraw(true);
|
self.buffer.set_redraw(true);
|
||||||
}
|
}
|
||||||
self.cursor_x_opt = None;
|
self.cursor_x_opt = None;
|
||||||
},
|
}
|
||||||
Action::Left => {
|
Action::Left => {
|
||||||
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
|
let rtl_opt = self.buffer.lines[self.cursor.line]
|
||||||
|
.shape_opt()
|
||||||
|
.as_ref()
|
||||||
|
.map(|shape| shape.rtl);
|
||||||
if let Some(rtl) = rtl_opt {
|
if let Some(rtl) = rtl_opt {
|
||||||
if rtl {
|
if rtl {
|
||||||
self.action(Action::Next);
|
self.action(Action::Next);
|
||||||
|
|
@ -305,9 +327,12 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
self.action(Action::Previous);
|
self.action(Action::Previous);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Action::Right => {
|
Action::Right => {
|
||||||
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
|
let rtl_opt = self.buffer.lines[self.cursor.line]
|
||||||
|
.shape_opt()
|
||||||
|
.as_ref()
|
||||||
|
.map(|shape| shape.rtl);
|
||||||
if let Some(rtl) = rtl_opt {
|
if let Some(rtl) = rtl_opt {
|
||||||
if rtl {
|
if rtl {
|
||||||
self.action(Action::Previous);
|
self.action(Action::Previous);
|
||||||
|
|
@ -315,14 +340,14 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
self.action(Action::Next);
|
self.action(Action::Next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Action::Up => {
|
Action::Up => {
|
||||||
//TODO: make this preserve X as best as possible!
|
//TODO: make this preserve X as best as possible!
|
||||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
||||||
|
|
||||||
if self.cursor_x_opt.is_none() {
|
if self.cursor_x_opt.is_none() {
|
||||||
self.cursor_x_opt = Some(
|
self.cursor_x_opt = Some(
|
||||||
cursor.glyph as i32 //TODO: glyph x position
|
cursor.glyph as i32, //TODO: glyph x position
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -338,16 +363,20 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_layout_cursor(cursor);
|
self.set_layout_cursor(cursor);
|
||||||
},
|
}
|
||||||
Action::Down => {
|
Action::Down => {
|
||||||
//TODO: make this preserve X as best as possible!
|
//TODO: make this preserve X as best as possible!
|
||||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
||||||
|
|
||||||
let layout_len = self.buffer.line_layout(cursor.line).expect("layout not found").len();
|
let layout_len = self
|
||||||
|
.buffer
|
||||||
|
.line_layout(cursor.line)
|
||||||
|
.expect("layout not found")
|
||||||
|
.len();
|
||||||
|
|
||||||
if self.cursor_x_opt.is_none() {
|
if self.cursor_x_opt.is_none() {
|
||||||
self.cursor_x_opt = Some(
|
self.cursor_x_opt = Some(
|
||||||
cursor.glyph as i32 //TODO: glyph x position
|
cursor.glyph as i32, //TODO: glyph x position
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -363,13 +392,13 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_layout_cursor(cursor);
|
self.set_layout_cursor(cursor);
|
||||||
},
|
}
|
||||||
Action::Home => {
|
Action::Home => {
|
||||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
||||||
cursor.glyph = 0;
|
cursor.glyph = 0;
|
||||||
self.set_layout_cursor(cursor);
|
self.set_layout_cursor(cursor);
|
||||||
self.cursor_x_opt = None;
|
self.cursor_x_opt = None;
|
||||||
},
|
}
|
||||||
Action::End => {
|
Action::End => {
|
||||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
||||||
cursor.glyph = usize::max_value();
|
cursor.glyph = usize::max_value();
|
||||||
|
|
@ -388,10 +417,10 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
}
|
}
|
||||||
Action::PageUp => {
|
Action::PageUp => {
|
||||||
self.action(Action::Vertical(-self.buffer.size().1));
|
self.action(Action::Vertical(-self.buffer.size().1));
|
||||||
},
|
}
|
||||||
Action::PageDown => {
|
Action::PageDown => {
|
||||||
self.action(Action::Vertical(self.buffer.size().1));
|
self.action(Action::Vertical(self.buffer.size().1));
|
||||||
},
|
}
|
||||||
Action::Vertical(px) => {
|
Action::Vertical(px) => {
|
||||||
// TODO more efficient
|
// TODO more efficient
|
||||||
let lines = px / self.buffer.metrics().line_height;
|
let lines = px / self.buffer.metrics().line_height;
|
||||||
|
|
@ -404,16 +433,14 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
self.action(Action::Down);
|
self.action(Action::Down);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Action::Escape => {
|
Action::Escape => {
|
||||||
if self.select_opt.take().is_some() {
|
if self.select_opt.take().is_some() {
|
||||||
self.buffer.set_redraw(true);
|
self.buffer.set_redraw(true);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Action::Insert(character) => {
|
Action::Insert(character) => {
|
||||||
if character.is_control()
|
if character.is_control() && !['\t', '\n', '\u{92}'].contains(&character) {
|
||||||
&& !['\t', '\n', '\u{92}'].contains(&character)
|
|
||||||
{
|
|
||||||
// Filter out special chars (except for tab), use Action instead
|
// Filter out special chars (except for tab), use Action instead
|
||||||
log::debug!("Refusing to insert control character {:?}", character);
|
log::debug!("Refusing to insert control character {:?}", character);
|
||||||
} else if character == '\n' {
|
} else if character == '\n' {
|
||||||
|
|
@ -423,7 +450,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
let str_ref = character.encode_utf8(&mut str_buf);
|
let str_ref = character.encode_utf8(&mut str_buf);
|
||||||
self.insert_string(str_ref, None);
|
self.insert_string(str_ref, None);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Action::Enter => {
|
Action::Enter => {
|
||||||
self.delete_selection();
|
self.delete_selection();
|
||||||
|
|
||||||
|
|
@ -433,7 +460,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
self.cursor.index = 0;
|
self.cursor.index = 0;
|
||||||
|
|
||||||
self.buffer.lines.insert(self.cursor.line, new_line);
|
self.buffer.lines.insert(self.cursor.line, new_line);
|
||||||
},
|
}
|
||||||
Action::Backspace => {
|
Action::Backspace => {
|
||||||
if self.delete_selection() {
|
if self.delete_selection() {
|
||||||
// Deleted selection
|
// Deleted selection
|
||||||
|
|
@ -472,7 +499,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
|
|
||||||
line.append(old_line);
|
line.append(old_line);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Action::Delete => {
|
Action::Delete => {
|
||||||
if self.delete_selection() {
|
if self.delete_selection() {
|
||||||
// Deleted selection
|
// Deleted selection
|
||||||
|
|
@ -484,9 +511,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
.grapheme_indices(true)
|
.grapheme_indices(true)
|
||||||
.take_while(|(i, _)| *i <= self.cursor.index)
|
.take_while(|(i, _)| *i <= self.cursor.index)
|
||||||
.last()
|
.last()
|
||||||
.map(|(i, c)| {
|
.map(|(i, c)| i..(i + c.len()));
|
||||||
i..(i + c.len())
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(range) = range_opt {
|
if let Some(range) = range_opt {
|
||||||
self.cursor.index = range.start;
|
self.cursor.index = range.start;
|
||||||
|
|
@ -504,7 +529,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
let old_line = self.buffer.lines.remove(self.cursor.line + 1);
|
let old_line = self.buffer.lines.remove(self.cursor.line + 1);
|
||||||
self.buffer.lines[self.cursor.line].append(old_line);
|
self.buffer.lines[self.cursor.line].append(old_line);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Action::Click { x, y } => {
|
Action::Click { x, y } => {
|
||||||
self.select_opt = None;
|
self.select_opt = None;
|
||||||
|
|
||||||
|
|
@ -514,7 +539,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
self.buffer.set_redraw(true);
|
self.buffer.set_redraw(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Action::Drag { x, y } => {
|
Action::Drag { x, y } => {
|
||||||
if self.select_opt.is_none() {
|
if self.select_opt.is_none() {
|
||||||
self.select_opt = Some(self.cursor);
|
self.select_opt = Some(self.cursor);
|
||||||
|
|
@ -527,7 +552,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
self.buffer.set_redraw(true);
|
self.buffer.set_redraw(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Action::Scroll { lines } => {
|
Action::Scroll { lines } => {
|
||||||
let mut scroll = self.buffer.scroll();
|
let mut scroll = self.buffer.scroll();
|
||||||
scroll += lines;
|
scroll += lines;
|
||||||
|
|
@ -573,7 +598,10 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
self.cursor_x_opt = None;
|
self.cursor_x_opt = None;
|
||||||
}
|
}
|
||||||
Action::LeftWord => {
|
Action::LeftWord => {
|
||||||
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
|
let rtl_opt = self.buffer.lines[self.cursor.line]
|
||||||
|
.shape_opt()
|
||||||
|
.as_ref()
|
||||||
|
.map(|shape| shape.rtl);
|
||||||
if let Some(rtl) = rtl_opt {
|
if let Some(rtl) = rtl_opt {
|
||||||
if rtl {
|
if rtl {
|
||||||
self.action(Action::NextWord);
|
self.action(Action::NextWord);
|
||||||
|
|
@ -581,9 +609,12 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
self.action(Action::PreviousWord);
|
self.action(Action::PreviousWord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Action::RightWord => {
|
Action::RightWord => {
|
||||||
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
|
let rtl_opt = self.buffer.lines[self.cursor.line]
|
||||||
|
.shape_opt()
|
||||||
|
.as_ref()
|
||||||
|
.map(|shape| shape.rtl);
|
||||||
if let Some(rtl) = rtl_opt {
|
if let Some(rtl) = rtl_opt {
|
||||||
if rtl {
|
if rtl {
|
||||||
self.action(Action::PreviousWord);
|
self.action(Action::PreviousWord);
|
||||||
|
|
@ -591,7 +622,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
self.action(Action::NextWord);
|
self.action(Action::NextWord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Action::BufferStart => {
|
Action::BufferStart => {
|
||||||
self.cursor.line = 0;
|
self.cursor.line = 0;
|
||||||
self.cursor.index = 0;
|
self.cursor.index = 0;
|
||||||
|
|
@ -628,7 +659,8 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
/// Draw the editor
|
/// Draw the editor
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
|
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
|
||||||
where F: FnMut(i32, i32, u32, u32, Color)
|
where
|
||||||
|
F: FnMut(i32, i32, u32, u32, Color),
|
||||||
{
|
{
|
||||||
let font_size = self.buffer.metrics().font_size;
|
let font_size = self.buffer.metrics().font_size;
|
||||||
let line_height = self.buffer.metrics().line_height;
|
let line_height = self.buffer.metrics().line_height;
|
||||||
|
|
@ -664,7 +696,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
if cursor.index == glyph.end {
|
if cursor.index == glyph.end {
|
||||||
return Some((run.glyphs.len(), 0.0));
|
return Some((run.glyphs.len(), 0.0));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
return Some((0, 0.0));
|
return Some((0, 0.0));
|
||||||
}
|
}
|
||||||
|
|
@ -701,16 +733,14 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
let c_start = glyph.start + i;
|
let c_start = glyph.start + i;
|
||||||
let c_end = glyph.start + i + c.len();
|
let c_end = glyph.start + i + c.len();
|
||||||
if (start.line != line_i || c_end > start.index)
|
if (start.line != line_i || c_end > start.index)
|
||||||
&& (end.line != line_i || c_start < end.index) {
|
&& (end.line != line_i || c_start < end.index)
|
||||||
|
{
|
||||||
range_opt = match range_opt.take() {
|
range_opt = match range_opt.take() {
|
||||||
Some((min, max)) => Some((
|
Some((min, max)) => Some((
|
||||||
cmp::min(min, c_x as i32),
|
cmp::min(min, c_x as i32),
|
||||||
cmp::max(max, (c_x + c_w) as i32),
|
cmp::max(max, (c_x + c_w) as i32),
|
||||||
)),
|
)),
|
||||||
None => Some((
|
None => Some((c_x as i32, (c_x + c_w) as i32)),
|
||||||
c_x as i32,
|
|
||||||
(c_x + c_w) as i32,
|
|
||||||
))
|
|
||||||
};
|
};
|
||||||
} else if let Some((min, max)) = range_opt.take() {
|
} else if let Some((min, max)) = range_opt.take() {
|
||||||
f(
|
f(
|
||||||
|
|
@ -718,14 +748,14 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
line_y - font_size,
|
line_y - font_size,
|
||||||
cmp::max(0, max - min) as u32,
|
cmp::max(0, max - min) as u32,
|
||||||
line_height as u32,
|
line_height as u32,
|
||||||
Color::rgba(color.r(), color.g(), color.b(), 0x33)
|
Color::rgba(color.r(), color.g(), color.b(), 0x33),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
c_x += c_w;
|
c_x += c_w;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if run.glyphs.is_empty() && end.line > line_i{
|
if run.glyphs.is_empty() && end.line > line_i {
|
||||||
// Highlight all of internal empty lines
|
// Highlight all of internal empty lines
|
||||||
range_opt = Some((0, self.buffer.size().0));
|
range_opt = Some((0, self.buffer.size().0));
|
||||||
}
|
}
|
||||||
|
|
@ -744,7 +774,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
line_y - font_size,
|
line_y - font_size,
|
||||||
cmp::max(0, max - min) as u32,
|
cmp::max(0, max - min) as u32,
|
||||||
line_height as u32,
|
line_height as u32,
|
||||||
Color::rgba(color.r(), color.g(), color.b(), 0x33)
|
Color::rgba(color.r(), color.g(), color.b(), 0x33),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -760,7 +790,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
} else {
|
} else {
|
||||||
(glyph.x + cursor_glyph_offset) as i32
|
(glyph.x + cursor_glyph_offset) as i32
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => match run.glyphs.last() {
|
None => match run.glyphs.last() {
|
||||||
Some(glyph) => {
|
Some(glyph) => {
|
||||||
// End of last glyph
|
// End of last glyph
|
||||||
|
|
@ -769,21 +799,15 @@ impl<'a> Edit<'a> for Editor<'a> {
|
||||||
} else {
|
} else {
|
||||||
(glyph.x + glyph.w) as i32
|
(glyph.x + glyph.w) as i32
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
// Start of empty line
|
// Start of empty line
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
f(
|
f(x, line_y - font_size, 1, line_height as u32, color);
|
||||||
x,
|
|
||||||
line_y - font_size,
|
|
||||||
1,
|
|
||||||
line_height as u32,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for glyph in run.glyphs.iter() {
|
for glyph in run.glyphs.iter() {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
|
|
||||||
use crate::{AttrsList, Buffer, Cursor};
|
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
use crate::Color;
|
use crate::Color;
|
||||||
|
use crate::{AttrsList, Buffer, Cursor};
|
||||||
|
|
||||||
pub use self::editor::*;
|
pub use self::editor::*;
|
||||||
mod editor;
|
mod editor;
|
||||||
|
|
@ -114,5 +114,6 @@ pub trait Edit<'a> {
|
||||||
/// Draw the editor
|
/// Draw the editor
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, f: F)
|
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, f: F)
|
||||||
where F: FnMut(i32, i32, u32, u32, Color);
|
where
|
||||||
|
F: FnMut(i32, i32, u32, u32, Color);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,13 @@
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::{
|
use alloc::{string::String, vec::Vec};
|
||||||
string::String,
|
|
||||||
vec::Vec,
|
|
||||||
};
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::{
|
use std::{fs, io, path::Path};
|
||||||
fs,
|
|
||||||
io,
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
use syntect::highlighting::{
|
use syntect::highlighting::{
|
||||||
FontStyle,
|
FontStyle, HighlightState, Highlighter, RangedHighlightIterator, Theme, ThemeSet,
|
||||||
Highlighter,
|
|
||||||
HighlightState,
|
|
||||||
RangedHighlightIterator,
|
|
||||||
Theme,
|
|
||||||
ThemeSet,
|
|
||||||
};
|
|
||||||
use syntect::parsing::{
|
|
||||||
ParseState,
|
|
||||||
ScopeStack,
|
|
||||||
SyntaxReference,
|
|
||||||
SyntaxSet,
|
|
||||||
};
|
};
|
||||||
|
use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
|
||||||
|
|
||||||
use crate::{
|
use crate::{Action, AttrsList, Buffer, Color, Cursor, Edit, Editor, Style, Weight, Wrap};
|
||||||
Action,
|
|
||||||
AttrsList,
|
|
||||||
Buffer,
|
|
||||||
Color,
|
|
||||||
Cursor,
|
|
||||||
Edit,
|
|
||||||
Editor,
|
|
||||||
Style,
|
|
||||||
Weight,
|
|
||||||
Wrap,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct SyntaxSystem {
|
pub struct SyntaxSystem {
|
||||||
pub syntax_set: SyntaxSet,
|
pub syntax_set: SyntaxSet,
|
||||||
|
|
@ -69,7 +41,11 @@ impl<'a> SyntaxEditor<'a> {
|
||||||
/// A good default theme name is "base16-eighties.dark".
|
/// A good default theme name is "base16-eighties.dark".
|
||||||
///
|
///
|
||||||
/// Returns None if theme not found
|
/// Returns None if theme not found
|
||||||
pub fn new(buffer: Buffer<'a>, syntax_system: &'a SyntaxSystem, theme_name: &str) -> Option<Self> {
|
pub fn new(
|
||||||
|
buffer: Buffer<'a>,
|
||||||
|
syntax_system: &'a SyntaxSystem,
|
||||||
|
theme_name: &str,
|
||||||
|
) -> Option<Self> {
|
||||||
let editor = Editor::new(buffer);
|
let editor = Editor::new(buffer);
|
||||||
let syntax = syntax_system.syntax_set.find_syntax_plain_text();
|
let syntax = syntax_system.syntax_set.find_syntax_plain_text();
|
||||||
let theme = syntax_system.theme_set.themes.get(theme_name)?;
|
let theme = syntax_system.theme_set.themes.get(theme_name)?;
|
||||||
|
|
@ -91,7 +67,11 @@ impl<'a> SyntaxEditor<'a> {
|
||||||
///
|
///
|
||||||
/// Returns an [`io::Error`] if reading the file fails
|
/// Returns an [`io::Error`] if reading the file fails
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub fn load_text<P: AsRef<Path>>(&mut self, path: P, attrs: crate::Attrs<'a>) -> io::Result<()> {
|
pub fn load_text<P: AsRef<Path>>(
|
||||||
|
&mut self,
|
||||||
|
path: P,
|
||||||
|
attrs: crate::Attrs<'a>,
|
||||||
|
) -> io::Result<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|
||||||
let text = fs::read_to_string(path)?;
|
let text = fs::read_to_string(path)?;
|
||||||
|
|
@ -119,12 +99,7 @@ impl<'a> SyntaxEditor<'a> {
|
||||||
/// Get the default background color
|
/// Get the default background color
|
||||||
pub fn background_color(&self) -> Color {
|
pub fn background_color(&self) -> Color {
|
||||||
if let Some(background) = self.theme.settings.background {
|
if let Some(background) = self.theme.settings.background {
|
||||||
Color::rgba(
|
Color::rgba(background.r, background.g, background.b, background.a)
|
||||||
background.r,
|
|
||||||
background.g,
|
|
||||||
background.b,
|
|
||||||
background.a,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Color::rgb(0, 0, 0)
|
Color::rgb(0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
@ -133,12 +108,7 @@ impl<'a> SyntaxEditor<'a> {
|
||||||
/// Get the default foreground (text) color
|
/// Get the default foreground (text) color
|
||||||
pub fn foreground_color(&self) -> Color {
|
pub fn foreground_color(&self) -> Color {
|
||||||
if let Some(foreground) = self.theme.settings.foreground {
|
if let Some(foreground) = self.theme.settings.foreground {
|
||||||
Color::rgba(
|
Color::rgba(foreground.r, foreground.g, foreground.b, foreground.a)
|
||||||
foreground.r,
|
|
||||||
foreground.g,
|
|
||||||
foreground.b,
|
|
||||||
foreground.a,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Color::rgb(0xFF, 0xFF, 0xFF)
|
Color::rgb(0xFF, 0xFF, 0xFF)
|
||||||
}
|
}
|
||||||
|
|
@ -175,21 +145,24 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
|
||||||
let mut highlighted = 0;
|
let mut highlighted = 0;
|
||||||
for line_i in 0..buffer.lines.len() {
|
for line_i in 0..buffer.lines.len() {
|
||||||
let line = &mut buffer.lines[line_i];
|
let line = &mut buffer.lines[line_i];
|
||||||
if ! line.is_reset() && line_i < self.syntax_cache.len() {
|
if !line.is_reset() && line_i < self.syntax_cache.len() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
highlighted += 1;
|
highlighted += 1;
|
||||||
|
|
||||||
let (mut parse_state, mut highlight_state) = if line_i > 0 && line_i <= self.syntax_cache.len() {
|
let (mut parse_state, mut highlight_state) =
|
||||||
self.syntax_cache[line_i - 1].clone()
|
if line_i > 0 && line_i <= self.syntax_cache.len() {
|
||||||
} else {
|
self.syntax_cache[line_i - 1].clone()
|
||||||
(
|
} else {
|
||||||
ParseState::new(self.syntax),
|
(
|
||||||
HighlightState::new(&self.highlighter, ScopeStack::new())
|
ParseState::new(self.syntax),
|
||||||
)
|
HighlightState::new(&self.highlighter, ScopeStack::new()),
|
||||||
};
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let ops = parse_state.parse_line(line.text(), &self.syntax_system.syntax_set).expect("failed to parse syntax");
|
let ops = parse_state
|
||||||
|
.parse_line(line.text(), &self.syntax_system.syntax_set)
|
||||||
|
.expect("failed to parse syntax");
|
||||||
let ranges = RangedHighlightIterator::new(
|
let ranges = RangedHighlightIterator::new(
|
||||||
&mut highlight_state,
|
&mut highlight_state,
|
||||||
&ops,
|
&ops,
|
||||||
|
|
@ -219,8 +192,7 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
|
||||||
Weight::BOLD
|
Weight::BOLD
|
||||||
} else {
|
} else {
|
||||||
Weight::NORMAL
|
Weight::NORMAL
|
||||||
})
|
}), //TODO: underline
|
||||||
//TODO: underline
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,7 +219,11 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
|
||||||
if highlighted > 0 {
|
if highlighted > 0 {
|
||||||
buffer.set_redraw(true);
|
buffer.set_redraw(true);
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
log::debug!("Syntax highlighted {} lines in {:?}", highlighted, now.elapsed());
|
log::debug!(
|
||||||
|
"Syntax highlighted {} lines in {:?}",
|
||||||
|
highlighted,
|
||||||
|
now.elapsed()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.editor.shape_as_needed();
|
self.editor.shape_as_needed();
|
||||||
|
|
@ -272,7 +248,8 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
|
||||||
/// Draw the editor
|
/// Draw the editor
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
fn draw<F>(&self, cache: &mut crate::SwashCache, _color: Color, mut f: F)
|
fn draw<F>(&self, cache: &mut crate::SwashCache, _color: Color, mut f: F)
|
||||||
where F: FnMut(i32, i32, u32, u32, Color)
|
where
|
||||||
|
F: FnMut(i32, i32, u32, u32, Color),
|
||||||
{
|
{
|
||||||
let size = self.buffer().size();
|
let size = self.buffer().size();
|
||||||
f(0, 0, size.0 as u32, size.1 as u32, self.background_color());
|
f(0, 0, size.0 as u32, size.1 as u32, self.background_color());
|
||||||
|
|
|
||||||
100
src/edit/vi.rs
100
src/edit/vi.rs
|
|
@ -2,15 +2,7 @@ use alloc::string::String;
|
||||||
use core::cmp;
|
use core::cmp;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::{
|
use crate::{Action, AttrsList, Buffer, Color, Cursor, Edit, SyntaxEditor};
|
||||||
Action,
|
|
||||||
AttrsList,
|
|
||||||
Buffer,
|
|
||||||
Color,
|
|
||||||
Cursor,
|
|
||||||
Edit,
|
|
||||||
SyntaxEditor,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
enum Mode {
|
enum Mode {
|
||||||
|
|
@ -36,7 +28,11 @@ impl<'a> ViEditor<'a> {
|
||||||
|
|
||||||
/// Load text from a file, and also set syntax to the best option
|
/// Load text from a file, and also set syntax to the best option
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub fn load_text<P: AsRef<std::path::Path>>(&mut self, path: P, attrs: crate::Attrs<'a>) -> std::io::Result<()> {
|
pub fn load_text<P: AsRef<std::path::Path>>(
|
||||||
|
&mut self,
|
||||||
|
path: P,
|
||||||
|
attrs: crate::Attrs<'a>,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
self.editor.load_text(path, attrs)
|
self.editor.load_text(path, attrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,12 +94,12 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
'a' => {
|
'a' => {
|
||||||
self.editor.action(Action::Right);
|
self.editor.action(Action::Right);
|
||||||
self.mode = Mode::Insert;
|
self.mode = Mode::Insert;
|
||||||
},
|
}
|
||||||
// Enter insert mode at end of line
|
// Enter insert mode at end of line
|
||||||
'A' => {
|
'A' => {
|
||||||
self.editor.action(Action::End);
|
self.editor.action(Action::End);
|
||||||
self.mode = Mode::Insert;
|
self.mode = Mode::Insert;
|
||||||
},
|
}
|
||||||
// Change mode
|
// Change mode
|
||||||
'c' => {
|
'c' => {
|
||||||
if self.editor.select_opt().is_some() {
|
if self.editor.select_opt().is_some() {
|
||||||
|
|
@ -112,7 +108,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
} else {
|
} else {
|
||||||
//TODO: change to next cursor movement
|
//TODO: change to next cursor movement
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// Delete mode
|
// Delete mode
|
||||||
'd' => {
|
'd' => {
|
||||||
if self.editor.select_opt().is_some() {
|
if self.editor.select_opt().is_some() {
|
||||||
|
|
@ -120,11 +116,11 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
} else {
|
} else {
|
||||||
//TODO: delete to next cursor movement
|
//TODO: delete to next cursor movement
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// Enter insert mode at cursor
|
// Enter insert mode at cursor
|
||||||
'i' => {
|
'i' => {
|
||||||
self.mode = Mode::Insert;
|
self.mode = Mode::Insert;
|
||||||
},
|
}
|
||||||
// Enter insert mode at start of line
|
// Enter insert mode at start of line
|
||||||
'I' => {
|
'I' => {
|
||||||
//TODO: soft home, skip whitespace
|
//TODO: soft home, skip whitespace
|
||||||
|
|
@ -136,7 +132,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
self.editor.action(Action::End);
|
self.editor.action(Action::End);
|
||||||
self.editor.action(Action::Enter);
|
self.editor.action(Action::Enter);
|
||||||
self.mode = Mode::Insert;
|
self.mode = Mode::Insert;
|
||||||
},
|
}
|
||||||
// Create line before and enter insert mode
|
// Create line before and enter insert mode
|
||||||
'O' => {
|
'O' => {
|
||||||
self.editor.action(Action::Home);
|
self.editor.action(Action::Home);
|
||||||
|
|
@ -144,7 +140,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
self.editor.shape_as_needed(); // TODO: do not require this?
|
self.editor.shape_as_needed(); // TODO: do not require this?
|
||||||
self.editor.action(Action::Up);
|
self.editor.action(Action::Up);
|
||||||
self.mode = Mode::Insert;
|
self.mode = Mode::Insert;
|
||||||
},
|
}
|
||||||
// Left
|
// Left
|
||||||
'h' => self.editor.action(Action::Left),
|
'h' => self.editor.action(Action::Left),
|
||||||
// Top of screen
|
// Top of screen
|
||||||
|
|
@ -166,7 +162,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
} else {
|
} else {
|
||||||
self.editor.set_select_opt(Some(self.editor.cursor()));
|
self.editor.set_select_opt(Some(self.editor.cursor()));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// Enter line visual mode
|
// Enter line visual mode
|
||||||
'V' => {
|
'V' => {
|
||||||
if self.editor.select_opt().is_some() {
|
if self.editor.select_opt().is_some() {
|
||||||
|
|
@ -177,7 +173,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
//TODO: set cursor_x_opt to max
|
//TODO: set cursor_x_opt to max
|
||||||
self.editor.action(Action::End);
|
self.editor.action(Action::End);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// Remove character at cursor
|
// Remove character at cursor
|
||||||
'x' => self.editor.action(Action::Delete),
|
'x' => self.editor.action(Action::Delete),
|
||||||
// Remove character before cursor
|
// Remove character before cursor
|
||||||
|
|
@ -192,15 +188,15 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
// Enter command mode
|
// Enter command mode
|
||||||
':' => {
|
':' => {
|
||||||
self.mode = Mode::Command;
|
self.mode = Mode::Command;
|
||||||
},
|
}
|
||||||
// Enter search mode
|
// Enter search mode
|
||||||
'/' => {
|
'/' => {
|
||||||
self.mode = Mode::Search;
|
self.mode = Mode::Search;
|
||||||
},
|
}
|
||||||
// Enter search backwards mode
|
// Enter search backwards mode
|
||||||
'?' => {
|
'?' => {
|
||||||
self.mode = Mode::SearchBackwards;
|
self.mode = Mode::SearchBackwards;
|
||||||
},
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
_ => self.editor.action(action),
|
_ => self.editor.action(action),
|
||||||
|
|
@ -213,13 +209,13 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
self.editor.action(Action::Left);
|
self.editor.action(Action::Left);
|
||||||
}
|
}
|
||||||
self.mode = Mode::Normal;
|
self.mode = Mode::Normal;
|
||||||
},
|
}
|
||||||
_ => self.editor.action(action),
|
_ => self.editor.action(action),
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
//TODO: other modes
|
//TODO: other modes
|
||||||
self.mode = Mode::Normal;
|
self.mode = Mode::Normal;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mode != old_mode {
|
if self.mode != old_mode {
|
||||||
|
|
@ -229,7 +225,8 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
|
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
|
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
|
||||||
where F: FnMut(i32, i32, u32, u32, Color)
|
where
|
||||||
|
F: FnMut(i32, i32, u32, u32, Color),
|
||||||
{
|
{
|
||||||
let font_size = self.buffer().metrics().font_size;
|
let font_size = self.buffer().metrics().font_size;
|
||||||
let line_height = self.buffer().metrics().line_height;
|
let line_height = self.buffer().metrics().line_height;
|
||||||
|
|
@ -266,7 +263,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
if cursor.index == glyph.end {
|
if cursor.index == glyph.end {
|
||||||
return Some((run.glyphs.len(), 0.0, default_width));
|
return Some((run.glyphs.len(), 0.0, default_width));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
return Some((0, 0.0, default_width));
|
return Some((0, 0.0, default_width));
|
||||||
}
|
}
|
||||||
|
|
@ -303,16 +300,14 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
let c_start = glyph.start + i;
|
let c_start = glyph.start + i;
|
||||||
let c_end = glyph.start + i + c.len();
|
let c_end = glyph.start + i + c.len();
|
||||||
if (start.line != line_i || c_end > start.index)
|
if (start.line != line_i || c_end > start.index)
|
||||||
&& (end.line != line_i || c_start < end.index) {
|
&& (end.line != line_i || c_start < end.index)
|
||||||
|
{
|
||||||
range_opt = match range_opt.take() {
|
range_opt = match range_opt.take() {
|
||||||
Some((min, max)) => Some((
|
Some((min, max)) => Some((
|
||||||
cmp::min(min, c_x as i32),
|
cmp::min(min, c_x as i32),
|
||||||
cmp::max(max, (c_x + c_w) as i32),
|
cmp::max(max, (c_x + c_w) as i32),
|
||||||
)),
|
)),
|
||||||
None => Some((
|
None => Some((c_x as i32, (c_x + c_w) as i32)),
|
||||||
c_x as i32,
|
|
||||||
(c_x + c_w) as i32,
|
|
||||||
))
|
|
||||||
};
|
};
|
||||||
} else if let Some((min, max)) = range_opt.take() {
|
} else if let Some((min, max)) = range_opt.take() {
|
||||||
f(
|
f(
|
||||||
|
|
@ -320,14 +315,14 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
line_y - font_size,
|
line_y - font_size,
|
||||||
cmp::max(0, max - min) as u32,
|
cmp::max(0, max - min) as u32,
|
||||||
line_height as u32,
|
line_height as u32,
|
||||||
Color::rgba(color.r(), color.g(), color.b(), 0x33)
|
Color::rgba(color.r(), color.g(), color.b(), 0x33),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
c_x += c_w;
|
c_x += c_w;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if run.glyphs.is_empty() && end.line > line_i{
|
if run.glyphs.is_empty() && end.line > line_i {
|
||||||
// Highlight all of internal empty lines
|
// Highlight all of internal empty lines
|
||||||
range_opt = Some((0, self.buffer().size().0));
|
range_opt = Some((0, self.buffer().size().0));
|
||||||
}
|
}
|
||||||
|
|
@ -346,18 +341,20 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
line_y - font_size,
|
line_y - font_size,
|
||||||
cmp::max(0, max - min) as u32,
|
cmp::max(0, max - min) as u32,
|
||||||
line_height as u32,
|
line_height as u32,
|
||||||
Color::rgba(color.r(), color.g(), color.b(), 0x33)
|
Color::rgba(color.r(), color.g(), color.b(), 0x33),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw cursor
|
// Draw cursor
|
||||||
if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) = cursor_glyph_opt(&self.cursor()) {
|
if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) =
|
||||||
|
cursor_glyph_opt(&self.cursor())
|
||||||
|
{
|
||||||
let block_cursor = match self.mode {
|
let block_cursor = match self.mode {
|
||||||
Mode::Normal => true,
|
Mode::Normal => true,
|
||||||
Mode::Insert => false,
|
Mode::Insert => false,
|
||||||
_ => true /*TODO: determine block cursor in other modes*/
|
_ => true, /*TODO: determine block cursor in other modes*/
|
||||||
};
|
};
|
||||||
|
|
||||||
let (start_x, end_x) = match run.glyphs.get(cursor_glyph) {
|
let (start_x, end_x) = match run.glyphs.get(cursor_glyph) {
|
||||||
|
|
@ -366,38 +363,33 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
if glyph.level.is_rtl() {
|
if glyph.level.is_rtl() {
|
||||||
(
|
(
|
||||||
(glyph.x + glyph.w - cursor_glyph_offset) as i32,
|
(glyph.x + glyph.w - cursor_glyph_offset) as i32,
|
||||||
(glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width) as i32,
|
(glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width)
|
||||||
|
as i32,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
(glyph.x + cursor_glyph_offset) as i32,
|
(glyph.x + cursor_glyph_offset) as i32,
|
||||||
(glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32
|
(glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => match run.glyphs.last() {
|
None => match run.glyphs.last() {
|
||||||
Some(glyph) => {
|
Some(glyph) => {
|
||||||
// End of last glyph
|
// End of last glyph
|
||||||
if glyph.level.is_rtl() {
|
if glyph.level.is_rtl() {
|
||||||
(
|
(glyph.x as i32, (glyph.x - cursor_glyph_width) as i32)
|
||||||
glyph.x as i32,
|
|
||||||
(glyph.x - cursor_glyph_width) as i32
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
(glyph.x + glyph.w) as i32,
|
(glyph.x + glyph.w) as i32,
|
||||||
(glyph.x + glyph.w + cursor_glyph_width) as i32
|
(glyph.x + glyph.w + cursor_glyph_width) as i32,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
// Start of empty line
|
// Start of empty line
|
||||||
(
|
(0, cursor_glyph_width as i32)
|
||||||
0,
|
|
||||||
cursor_glyph_width as i32
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if block_cursor {
|
if block_cursor {
|
||||||
|
|
@ -411,13 +403,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
||||||
Color::rgba(color.r(), color.g(), color.b(), 0x33),
|
Color::rgba(color.r(), color.g(), color.b(), 0x33),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
f(
|
f(start_x, line_y - font_size, 1, line_height as u32, color);
|
||||||
start_x,
|
|
||||||
line_y - font_size,
|
|
||||||
1,
|
|
||||||
line_height as u32,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@ pub fn common_fallback() -> &'static [&'static str] {
|
||||||
|
|
||||||
// Fallbacks to never use
|
// Fallbacks to never use
|
||||||
pub fn forbidden_fallback() -> &'static [&'static str] {
|
pub fn forbidden_fallback() -> &'static [&'static str] {
|
||||||
&[
|
&[".LastResort"]
|
||||||
".LastResort",
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn han_unification(locale: &str) -> &'static [&'static str] {
|
fn han_unification(locale: &str) -> &'static [&'static str] {
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,7 @@ use crate::Font;
|
||||||
|
|
||||||
use self::platform::*;
|
use self::platform::*;
|
||||||
|
|
||||||
#[cfg(not(any(
|
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows",)))]
|
||||||
target_os = "linux",
|
|
||||||
target_os = "macos",
|
|
||||||
target_os = "windows",
|
|
||||||
)))]
|
|
||||||
#[path = "other.rs"]
|
#[path = "other.rs"]
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
||||||
|
|
@ -46,7 +42,7 @@ impl<'a> FontFallbackIter<'a> {
|
||||||
fonts: &'a [Arc<Font<'a>>],
|
fonts: &'a [Arc<Font<'a>>],
|
||||||
default_families: &'a [&'a str],
|
default_families: &'a [&'a str],
|
||||||
scripts: Vec<Script>,
|
scripts: Vec<Script>,
|
||||||
locale: &'a str
|
locale: &'a str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fonts,
|
fonts,
|
||||||
|
|
@ -78,7 +74,7 @@ impl<'a> FontFallbackIter<'a> {
|
||||||
font.info.family,
|
font.info.family,
|
||||||
word
|
word
|
||||||
);
|
);
|
||||||
} else if ! self.scripts.is_empty() && self.common_i > 0 {
|
} else if !self.scripts.is_empty() && self.common_i > 0 {
|
||||||
let family = common_fallback()[self.common_i - 1];
|
let family = common_fallback()[self.common_i - 1];
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Failed to find script fallback for {:?} locale '{}', used '{}': '{}'",
|
"Failed to find script fallback for {:?} locale '{}', used '{}': '{}'",
|
||||||
|
|
@ -117,7 +113,12 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
||||||
return Some(font);
|
return Some(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log::warn!("failed to find family '{}' for script {:?} and locale '{}'", script_family, script, self.locale);
|
log::warn!(
|
||||||
|
"failed to find family '{}' for script {:?} and locale '{}'",
|
||||||
|
script_family,
|
||||||
|
script,
|
||||||
|
self.locale
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.script_i.0 += 1;
|
self.script_i.0 += 1;
|
||||||
|
|
@ -142,7 +143,7 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
||||||
while self.other_i < self.fonts.len() {
|
while self.other_i < self.fonts.len() {
|
||||||
let font = &self.fonts[self.other_i];
|
let font = &self.fonts[self.other_i];
|
||||||
self.other_i += 1;
|
self.other_i += 1;
|
||||||
if ! forbidden_families.contains(&font.info.family.as_str()) {
|
if !forbidden_families.contains(&font.info.family.as_str()) {
|
||||||
return Some(font);
|
return Some(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ fn han_unification(locale: &str) -> &'static [&'static str] {
|
||||||
// Taiwan
|
// Taiwan
|
||||||
"zh-TW" => &["Microsoft JhengHei UI"],
|
"zh-TW" => &["Microsoft JhengHei UI"],
|
||||||
// Simplified Chinese is the default (also catches "zh-CN" for China)
|
// Simplified Chinese is the default (also catches "zh-CN" for China)
|
||||||
_ => &["Microsoft YaHei UI"]
|
_ => &["Microsoft YaHei UI"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,7 @@
|
||||||
|
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::{
|
use alloc::{string::String, vec::Vec};
|
||||||
string::String,
|
|
||||||
vec::Vec,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::Font;
|
use crate::Font;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use alloc::{
|
||||||
use crate::{Attrs, Font, FontMatches};
|
use crate::{Attrs, Font, FontMatches};
|
||||||
|
|
||||||
/// Access system fonts
|
/// Access system fonts
|
||||||
pub struct FontSystem{
|
pub struct FontSystem {
|
||||||
locale: String,
|
locale: String,
|
||||||
db: fontdb::Database,
|
db: fontdb::Database,
|
||||||
}
|
}
|
||||||
|
|
@ -25,16 +25,13 @@ impl FontSystem {
|
||||||
db.set_serif_family("DejaVu Serif");
|
db.set_serif_family("DejaVu Serif");
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self { locale, db }
|
||||||
locale,
|
|
||||||
db,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_locale_and_db(locale: &str, db: fontdb::Database) -> Self {
|
pub fn new_with_locale_and_db(locale: &str, db: fontdb::Database) -> Self {
|
||||||
Self {
|
Self {
|
||||||
locale: locale.to_string(),
|
locale: locale.to_string(),
|
||||||
db
|
db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +71,7 @@ impl FontSystem {
|
||||||
Arc::new(FontMatches {
|
Arc::new(FontMatches {
|
||||||
locale: &self.locale,
|
locale: &self.locale,
|
||||||
default_family: self.db.family_name(&attrs.family).to_string(),
|
default_family: self.db.family_name(&attrs.family).to_string(),
|
||||||
fonts
|
fonts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use alloc::{
|
||||||
use crate::{Attrs, Font, FontMatches};
|
use crate::{Attrs, Font, FontMatches};
|
||||||
|
|
||||||
/// Access system fonts
|
/// Access system fonts
|
||||||
pub struct FontSystem{
|
pub struct FontSystem {
|
||||||
locale: String,
|
locale: String,
|
||||||
db: fontdb::Database,
|
db: fontdb::Database,
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +46,9 @@ impl FontSystem {
|
||||||
//TODO only do this on demand!
|
//TODO only do this on demand!
|
||||||
for i in 0..db.faces().len() {
|
for i in 0..db.faces().len() {
|
||||||
let id = db.faces()[i].id;
|
let id = db.faces()[i].id;
|
||||||
unsafe { db.make_shared_face_data(id); }
|
unsafe {
|
||||||
|
db.make_shared_face_data(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|
@ -56,10 +58,7 @@ impl FontSystem {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self { locale, db }
|
||||||
locale,
|
|
||||||
db,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn locale(&self) -> &str {
|
pub fn locale(&self) -> &str {
|
||||||
|
|
@ -98,7 +97,7 @@ impl FontSystem {
|
||||||
Arc::new(FontMatches {
|
Arc::new(FontMatches {
|
||||||
locale: &self.locale,
|
locale: &self.locale,
|
||||||
default_family: self.db.family_name(&attrs.family).to_string(),
|
default_family: self.db.family_name(&attrs.family).to_string(),
|
||||||
fonts
|
fonts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,9 @@ impl FontSystem {
|
||||||
//TODO only do this on demand!
|
//TODO only do this on demand!
|
||||||
for i in 0..db.faces().len() {
|
for i in 0..db.faces().len() {
|
||||||
let id = db.faces()[i].id;
|
let id = db.faces()[i].id;
|
||||||
unsafe { db.make_shared_face_data(id); }
|
unsafe {
|
||||||
|
db.make_shared_face_data(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|
@ -69,12 +71,15 @@ impl FontSystem {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self(FontSystemInnerBuilder {
|
Self(
|
||||||
locale,
|
FontSystemInnerBuilder {
|
||||||
db,
|
locale,
|
||||||
font_cache_builder: |_| Mutex::new(HashMap::new()),
|
db,
|
||||||
font_matches_cache_builder: |_, _| Mutex::new(HashMap::new())
|
font_cache_builder: |_| Mutex::new(HashMap::new()),
|
||||||
}.build())
|
font_matches_cache_builder: |_, _| Mutex::new(HashMap::new()),
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn locale(&self) -> &str {
|
pub fn locale(&self) -> &str {
|
||||||
|
|
@ -93,53 +98,66 @@ impl FontSystem {
|
||||||
// Clippy false positive
|
// Clippy false positive
|
||||||
#[allow(clippy::needless_lifetimes)]
|
#[allow(clippy::needless_lifetimes)]
|
||||||
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
|
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
|
||||||
self.0.with(|fields| {
|
self.0.with(|fields| get_font(&fields, id))
|
||||||
get_font(&fields, id)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
|
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
|
||||||
self.0.with(|fields| {
|
self.0.with(|fields| {
|
||||||
let mut font_matches_cache = fields.font_matches_cache.lock().expect("failed to lock font matches cache");
|
let mut font_matches_cache = fields
|
||||||
|
.font_matches_cache
|
||||||
|
.lock()
|
||||||
|
.expect("failed to lock font matches cache");
|
||||||
//TODO: do not create AttrsOwned unless entry does not already exist
|
//TODO: do not create AttrsOwned unless entry does not already exist
|
||||||
font_matches_cache.entry(AttrsOwned::new(attrs)).or_insert_with(|| {
|
font_matches_cache
|
||||||
let now = std::time::Instant::now();
|
.entry(AttrsOwned::new(attrs))
|
||||||
|
.or_insert_with(|| {
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
let mut fonts = Vec::new();
|
let mut fonts = Vec::new();
|
||||||
for face in fields.db.faces() {
|
for face in fields.db.faces() {
|
||||||
if !attrs.matches(face) {
|
if !attrs.matches(face) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(font) = get_font(&fields, face.id) {
|
||||||
|
fonts.push(font);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(font) = get_font(&fields, face.id) {
|
let font_matches = Arc::new(FontMatches {
|
||||||
fonts.push(font);
|
locale: fields.locale,
|
||||||
}
|
default_family: fields.db.family_name(&attrs.family).to_string(),
|
||||||
}
|
fonts,
|
||||||
|
});
|
||||||
|
|
||||||
let font_matches = Arc::new(FontMatches {
|
let elapsed = now.elapsed();
|
||||||
locale: fields.locale,
|
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
|
||||||
default_family: fields.db.family_name(&attrs.family).to_string(),
|
|
||||||
fonts
|
|
||||||
});
|
|
||||||
|
|
||||||
let elapsed = now.elapsed();
|
font_matches
|
||||||
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
|
})
|
||||||
|
.clone()
|
||||||
font_matches
|
|
||||||
}).clone()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_font<'b>(fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>, id: fontdb::ID) -> Option<Arc<Font<'b>>> {
|
fn get_font<'b>(
|
||||||
fields.font_cache.lock().expect("failed to lock font cache").entry(id).or_insert_with(|| {
|
fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>,
|
||||||
let face = fields.db.face(id)?;
|
id: fontdb::ID,
|
||||||
match Font::new(face) {
|
) -> Option<Arc<Font<'b>>> {
|
||||||
Some(font) => Some(Arc::new(font)),
|
fields
|
||||||
None => {
|
.font_cache
|
||||||
log::warn!("failed to load font '{}'", face.post_script_name);
|
.lock()
|
||||||
None
|
.expect("failed to lock font cache")
|
||||||
|
.entry(id)
|
||||||
|
.or_insert_with(|| {
|
||||||
|
let face = fields.db.face(id)?;
|
||||||
|
match Font::new(face) {
|
||||||
|
Some(font) => Some(Arc::new(font)),
|
||||||
|
None => {
|
||||||
|
log::warn!("failed to load font '{}'", face.post_script_name);
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}).clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,14 +56,12 @@
|
||||||
|
|
||||||
// Not interested in these lints
|
// Not interested in these lints
|
||||||
#![allow(clippy::new_without_default)]
|
#![allow(clippy::new_without_default)]
|
||||||
|
|
||||||
// TODO: address ocurrances and then deny
|
// TODO: address ocurrances and then deny
|
||||||
//
|
//
|
||||||
// Indexing a slice can cause panics and that is something we always want to avoid
|
// Indexing a slice can cause panics and that is something we always want to avoid
|
||||||
#![allow(clippy::indexing_slicing)]
|
#![allow(clippy::indexing_slicing)]
|
||||||
// Overflows can produce unpredictable results and are only checked in debug builds
|
// Overflows can produce unpredictable results and are only checked in debug builds
|
||||||
#![allow(clippy::integer_arithmetic)]
|
#![allow(clippy::integer_arithmetic)]
|
||||||
|
|
||||||
// Soundness issues
|
// Soundness issues
|
||||||
//
|
//
|
||||||
// Dereferencing unalinged pointers may be undefined behavior
|
// Dereferencing unalinged pointers may be undefined behavior
|
||||||
|
|
@ -75,7 +73,6 @@
|
||||||
#![deny(unreachable_patterns)]
|
#![deny(unreachable_patterns)]
|
||||||
// Ensure that all must_use results are used
|
// Ensure that all must_use results are used
|
||||||
#![deny(unused_must_use)]
|
#![deny(unused_must_use)]
|
||||||
|
|
||||||
// Style issues
|
// Style issues
|
||||||
//
|
//
|
||||||
// Documentation not ideal
|
// Documentation not ideal
|
||||||
|
|
@ -88,7 +85,6 @@
|
||||||
#![warn(clippy::semicolon_if_nothing_returned)]
|
#![warn(clippy::semicolon_if_nothing_returned)]
|
||||||
// Ensure numbers are readable
|
// Ensure numbers are readable
|
||||||
#![warn(clippy::unreadable_literal)]
|
#![warn(clippy::unreadable_literal)]
|
||||||
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
|
||||||
214
src/shape.rs
214
src/shape.rs
|
|
@ -2,14 +2,14 @@
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::cmp::{min, max};
|
use core::cmp::{max, min};
|
||||||
use core::mem;
|
use core::mem;
|
||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
use unicode_script::{Script, UnicodeScript};
|
use unicode_script::{Script, UnicodeScript};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::{AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap};
|
|
||||||
use crate::fallback::FontFallbackIter;
|
use crate::fallback::FontFallbackIter;
|
||||||
|
use crate::{AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap};
|
||||||
|
|
||||||
fn shape_fallback(
|
fn shape_fallback(
|
||||||
font: &Font,
|
font: &Font,
|
||||||
|
|
@ -110,21 +110,16 @@ fn shape_run<'a>(
|
||||||
let mut scripts = Vec::new();
|
let mut scripts = Vec::new();
|
||||||
for c in line[start_run..end_run].chars() {
|
for c in line[start_run..end_run].chars() {
|
||||||
match c.script() {
|
match c.script() {
|
||||||
Script::Common |
|
Script::Common | Script::Inherited | Script::Latin | Script::Unknown => (),
|
||||||
Script::Inherited |
|
script => {
|
||||||
Script::Latin |
|
if !scripts.contains(&script) {
|
||||||
Script::Unknown => (),
|
scripts.push(script);
|
||||||
script => if ! scripts.contains(&script) {
|
}
|
||||||
scripts.push(script);
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!(
|
log::trace!(" Run {:?}: '{}'", scripts, &line[start_run..end_run],);
|
||||||
" Run {:?}: '{}'",
|
|
||||||
scripts,
|
|
||||||
&line[start_run..end_run],
|
|
||||||
);
|
|
||||||
|
|
||||||
let attrs = attrs_list.get_span(start_run);
|
let attrs = attrs_list.get_span(start_run);
|
||||||
|
|
||||||
|
|
@ -135,7 +130,7 @@ fn shape_run<'a>(
|
||||||
&font_matches.fonts,
|
&font_matches.fonts,
|
||||||
&default_families,
|
&default_families,
|
||||||
scripts,
|
scripts,
|
||||||
font_matches.locale
|
font_matches.locale,
|
||||||
);
|
);
|
||||||
|
|
||||||
let (mut glyphs, mut missing) = shape_fallback(
|
let (mut glyphs, mut missing) = shape_fallback(
|
||||||
|
|
@ -155,14 +150,8 @@ fn shape_run<'a>(
|
||||||
};
|
};
|
||||||
|
|
||||||
log::trace!("Evaluating fallback with font '{}'", font.info.family);
|
log::trace!("Evaluating fallback with font '{}'", font.info.family);
|
||||||
let (mut fb_glyphs, fb_missing) = shape_fallback(
|
let (mut fb_glyphs, fb_missing) =
|
||||||
font,
|
shape_fallback(font, line, attrs_list, start_run, end_run, span_rtl);
|
||||||
line,
|
|
||||||
attrs_list,
|
|
||||||
start_run,
|
|
||||||
end_run,
|
|
||||||
span_rtl,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Insert all matching glyphs
|
// Insert all matching glyphs
|
||||||
let mut fb_i = 0;
|
let mut fb_i = 0;
|
||||||
|
|
@ -255,7 +244,7 @@ impl ShapeGlyph {
|
||||||
self.font_id,
|
self.font_id,
|
||||||
self.glyph_id,
|
self.glyph_id,
|
||||||
font_size,
|
font_size,
|
||||||
(x + x_offset, y - y_offset)
|
(x + x_offset, y - y_offset),
|
||||||
);
|
);
|
||||||
LayoutGlyph {
|
LayoutGlyph {
|
||||||
start: self.start,
|
start: self.start,
|
||||||
|
|
@ -307,7 +296,7 @@ impl ShapeWord {
|
||||||
for (egc_i, _egc) in word.grapheme_indices(true) {
|
for (egc_i, _egc) in word.grapheme_indices(true) {
|
||||||
let start_egc = word_range.start + egc_i;
|
let start_egc = word_range.start + egc_i;
|
||||||
let attrs_egc = attrs_list.get_span(start_egc);
|
let attrs_egc = attrs_list.get_span(start_egc);
|
||||||
if ! attrs.compatible(&attrs_egc) {
|
if !attrs.compatible(&attrs_egc) {
|
||||||
//TODO: more efficient
|
//TODO: more efficient
|
||||||
glyphs.append(&mut shape_run(
|
glyphs.append(&mut shape_run(
|
||||||
font_system,
|
font_system,
|
||||||
|
|
@ -315,7 +304,7 @@ impl ShapeWord {
|
||||||
attrs_list,
|
attrs_list,
|
||||||
start_run,
|
start_run,
|
||||||
start_egc,
|
start_egc,
|
||||||
span_rtl
|
span_rtl,
|
||||||
));
|
));
|
||||||
|
|
||||||
start_run = start_egc;
|
start_run = start_egc;
|
||||||
|
|
@ -330,7 +319,7 @@ impl ShapeWord {
|
||||||
attrs_list,
|
attrs_list,
|
||||||
start_run,
|
start_run,
|
||||||
word_range.end,
|
word_range.end,
|
||||||
span_rtl
|
span_rtl,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -341,7 +330,12 @@ impl ShapeWord {
|
||||||
y_advance += glyph.y_advance;
|
y_advance += glyph.y_advance;
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { blank, glyphs, x_advance, y_advance}
|
Self {
|
||||||
|
blank,
|
||||||
|
glyphs,
|
||||||
|
x_advance,
|
||||||
|
y_advance,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,7 +392,8 @@ impl ShapeSpan {
|
||||||
font_system,
|
font_system,
|
||||||
line,
|
line,
|
||||||
attrs_list,
|
attrs_list,
|
||||||
(span_range.start + start_lb + i)..(span_range.start + start_lb + i + c.len_utf8()),
|
(span_range.start + start_lb + i)
|
||||||
|
..(span_range.start + start_lb + i + c.len_utf8()),
|
||||||
level,
|
level,
|
||||||
true,
|
true,
|
||||||
));
|
));
|
||||||
|
|
@ -419,10 +414,7 @@ impl ShapeSpan {
|
||||||
words.reverse();
|
words.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
ShapeSpan {
|
ShapeSpan { level, words }
|
||||||
level,
|
|
||||||
words,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -436,11 +428,7 @@ pub struct ShapeLine {
|
||||||
type VlRange = (usize, (usize, usize), (usize, usize));
|
type VlRange = (usize, (usize, usize), (usize, usize));
|
||||||
|
|
||||||
impl ShapeLine {
|
impl ShapeLine {
|
||||||
pub fn new<'a>(
|
pub fn new<'a>(font_system: &'a FontSystem, line: &str, attrs_list: &AttrsList) -> Self {
|
||||||
font_system: &'a FontSystem,
|
|
||||||
line: &str,
|
|
||||||
attrs_list: &AttrsList
|
|
||||||
) -> Self {
|
|
||||||
let mut spans = Vec::new();
|
let mut spans = Vec::new();
|
||||||
|
|
||||||
let bidi = unicode_bidi::BidiInfo::new(line, None);
|
let bidi = unicode_bidi::BidiInfo::new(line, None);
|
||||||
|
|
@ -461,7 +449,12 @@ impl ShapeLine {
|
||||||
let mut start = line_range.start;
|
let mut start = line_range.start;
|
||||||
let mut run_level = levels[start];
|
let mut run_level = levels[start];
|
||||||
|
|
||||||
for (i, &new_level) in levels.iter().enumerate().take(line_range.end).skip(start + 1) {
|
for (i, &new_level) in levels
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.take(line_range.end)
|
||||||
|
.skip(start + 1)
|
||||||
|
{
|
||||||
if new_level != run_level {
|
if new_level != run_level {
|
||||||
// End of the previous run, start of a new one.
|
// End of the previous run, start of a new one.
|
||||||
spans.push(ShapeSpan::new(
|
spans.push(ShapeSpan::new(
|
||||||
|
|
@ -487,13 +480,11 @@ impl ShapeLine {
|
||||||
line_rtl
|
line_rtl
|
||||||
};
|
};
|
||||||
|
|
||||||
Self { rtl, spans}
|
Self { rtl, spans }
|
||||||
}
|
}
|
||||||
|
|
||||||
// A modified version of first part of unicode_bidi::bidi_info::visual_run
|
// A modified version of first part of unicode_bidi::bidi_info::visual_run
|
||||||
fn adjust_levels(
|
fn adjust_levels(para: &unicode_bidi::Paragraph) -> Vec<unicode_bidi::Level> {
|
||||||
para: &unicode_bidi::Paragraph,
|
|
||||||
) -> Vec<unicode_bidi::Level> {
|
|
||||||
use unicode_bidi::BidiClass::*;
|
use unicode_bidi::BidiClass::*;
|
||||||
let text = para.info.text;
|
let text = para.info.text;
|
||||||
let levels = ¶.info.levels;
|
let levels = ¶.info.levels;
|
||||||
|
|
@ -547,7 +538,10 @@ impl ShapeLine {
|
||||||
|
|
||||||
// A modified version of second part of unicode_bidi::bidi_info::visual run
|
// A modified version of second part of unicode_bidi::bidi_info::visual run
|
||||||
fn reorder(&self, line_range: &[VlRange]) -> Vec<Range<usize>> {
|
fn reorder(&self, line_range: &[VlRange]) -> Vec<Range<usize>> {
|
||||||
let line : Vec<unicode_bidi::Level> = line_range.iter().map(|(span_index, _, _)| self.spans[*span_index].level).collect();
|
let line: Vec<unicode_bidi::Level> = line_range
|
||||||
|
.iter()
|
||||||
|
.map(|(span_index, _, _)| self.spans[*span_index].level)
|
||||||
|
.collect();
|
||||||
// Find consecutive level runs.
|
// Find consecutive level runs.
|
||||||
let mut runs = Vec::new();
|
let mut runs = Vec::new();
|
||||||
let mut start = 0;
|
let mut start = 0;
|
||||||
|
|
@ -606,13 +600,7 @@ impl ShapeLine {
|
||||||
runs
|
runs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn layout(&self, font_size: i32, line_width: i32, wrap: Wrap) -> Vec<LayoutLine> {
|
||||||
pub fn layout(
|
|
||||||
&self,
|
|
||||||
font_size: i32,
|
|
||||||
line_width: i32,
|
|
||||||
wrap: Wrap,
|
|
||||||
) -> Vec<LayoutLine> {
|
|
||||||
let mut layout_lines = Vec::with_capacity(1);
|
let mut layout_lines = Vec::with_capacity(1);
|
||||||
|
|
||||||
// This is used to create a visual line for empty lines (e.g. lines with only a <CR>)
|
// This is used to create a visual line for empty lines (e.g. lines with only a <CR>)
|
||||||
|
|
@ -627,8 +615,6 @@ impl ShapeLine {
|
||||||
let mut x = start_x;
|
let mut x = start_x;
|
||||||
let mut y;
|
let mut y;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// This would keep the maximum number of spans that would fit on a visual line
|
// This would keep the maximum number of spans that would fit on a visual line
|
||||||
// If one span is too large, this variable will hold the range of words inside that span
|
// If one span is too large, this variable will hold the range of words inside that span
|
||||||
// that fits on a line.
|
// that fits on a line.
|
||||||
|
|
@ -636,22 +622,22 @@ impl ShapeLine {
|
||||||
|
|
||||||
if wrap == Wrap::None {
|
if wrap == Wrap::None {
|
||||||
for (span_index, span) in self.spans.iter().enumerate() {
|
for (span_index, span) in self.spans.iter().enumerate() {
|
||||||
current_visual_line.push((span_index, (0,0), (span.words.len(), 0)));
|
current_visual_line.push((span_index, (0, 0), (span.words.len(), 0)));
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
let mut fit_x = line_width as f32;
|
let mut fit_x = line_width as f32;
|
||||||
for (span_index, span) in self.spans.iter().enumerate() {
|
for (span_index, span) in self.spans.iter().enumerate() {
|
||||||
|
|
||||||
let mut word_ranges = Vec::new();
|
let mut word_ranges = Vec::new();
|
||||||
let mut word_range_width = 0.;
|
let mut word_range_width = 0.;
|
||||||
|
|
||||||
// Create the word ranges that fits in a visual line
|
// Create the word ranges that fits in a visual line
|
||||||
if self.rtl != span.level.is_rtl() { // incongruent directions
|
if self.rtl != span.level.is_rtl() {
|
||||||
|
// incongruent directions
|
||||||
let mut fitting_start = (span.words.len(), 0);
|
let mut fitting_start = (span.words.len(), 0);
|
||||||
for (i, word) in span.words.iter().enumerate().rev() {
|
for (i, word) in span.words.iter().enumerate().rev() {
|
||||||
let word_size = font_size as f32 * word.x_advance;
|
let word_size = font_size as f32 * word.x_advance;
|
||||||
if fit_x - word_size >= 0. { // fits
|
if fit_x - word_size >= 0. {
|
||||||
|
// fits
|
||||||
fit_x -= word_size;
|
fit_x -= word_size;
|
||||||
word_range_width += word_size;
|
word_range_width += word_size;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -663,33 +649,39 @@ impl ShapeLine {
|
||||||
word_range_width += glyph_size;
|
word_range_width += glyph_size;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
word_ranges.push(((i, glyph_i+1), fitting_start, word_range_width));
|
word_ranges.push((
|
||||||
|
(i, glyph_i + 1),
|
||||||
|
fitting_start,
|
||||||
|
word_range_width,
|
||||||
|
));
|
||||||
fit_x = line_width as f32 - glyph_size;
|
fit_x = line_width as f32 - glyph_size;
|
||||||
word_range_width = glyph_size;
|
word_range_width = glyph_size;
|
||||||
fitting_start = (i, glyph_i+1);
|
fitting_start = (i, glyph_i + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // Wrap::Word
|
} else {
|
||||||
word_ranges.push(((i+1, 0), fitting_start, word_range_width));
|
// Wrap::Word
|
||||||
|
word_ranges.push(((i + 1, 0), fitting_start, word_range_width));
|
||||||
|
|
||||||
if word.blank {
|
if word.blank {
|
||||||
fit_x = line_width as f32;
|
fit_x = line_width as f32;
|
||||||
word_range_width = 0.;
|
word_range_width = 0.;
|
||||||
fitting_start = (i+1, 0);
|
fitting_start = (i + 1, 0);
|
||||||
} else {
|
} else {
|
||||||
fit_x = line_width as f32 - word_size;
|
fit_x = line_width as f32 - word_size;
|
||||||
word_range_width = word_size;
|
word_range_width = word_size;
|
||||||
fitting_start = (i+1, 0);
|
fitting_start = (i + 1, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
word_ranges.push(((0,0),fitting_start, word_range_width));
|
word_ranges.push(((0, 0), fitting_start, word_range_width));
|
||||||
|
} else {
|
||||||
} else { // congruent direction
|
// congruent direction
|
||||||
let mut fitting_start = (0,0);
|
let mut fitting_start = (0, 0);
|
||||||
for (i, word) in span.words.iter().enumerate() {
|
for (i, word) in span.words.iter().enumerate() {
|
||||||
let word_size = font_size as f32 * word.x_advance;
|
let word_size = font_size as f32 * word.x_advance;
|
||||||
if fit_x - word_size >= 0. { // fits
|
if fit_x - word_size >= 0. {
|
||||||
|
// fits
|
||||||
fit_x -= word_size;
|
fit_x -= word_size;
|
||||||
word_range_width += word_size;
|
word_range_width += word_size;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -701,19 +693,24 @@ impl ShapeLine {
|
||||||
word_range_width += glyph_size;
|
word_range_width += glyph_size;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
word_ranges.push((fitting_start, (i, glyph_i), word_range_width));
|
word_ranges.push((
|
||||||
|
fitting_start,
|
||||||
|
(i, glyph_i),
|
||||||
|
word_range_width,
|
||||||
|
));
|
||||||
fit_x = line_width as f32 - glyph_size;
|
fit_x = line_width as f32 - glyph_size;
|
||||||
word_range_width = glyph_size;
|
word_range_width = glyph_size;
|
||||||
fitting_start = (i, glyph_i);
|
fitting_start = (i, glyph_i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // Wrap::Word
|
} else {
|
||||||
word_ranges.push((fitting_start,(i,0), word_range_width));
|
// Wrap::Word
|
||||||
|
word_ranges.push((fitting_start, (i, 0), word_range_width));
|
||||||
|
|
||||||
if word.blank {
|
if word.blank {
|
||||||
fit_x = line_width as f32;
|
fit_x = line_width as f32;
|
||||||
word_range_width = 0.;
|
word_range_width = 0.;
|
||||||
fitting_start = (i+1, 0);
|
fitting_start = (i + 1, 0);
|
||||||
} else {
|
} else {
|
||||||
fit_x = line_width as f32 - word_size;
|
fit_x = line_width as f32 - word_size;
|
||||||
word_range_width = word_size;
|
word_range_width = word_size;
|
||||||
|
|
@ -725,40 +722,52 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a visual line
|
// Create a visual line
|
||||||
for ((starting_word, starting_glyph), (ending_word, ending_glyph), word_range_width) in word_ranges {
|
for (
|
||||||
|
(starting_word, starting_glyph),
|
||||||
|
(ending_word, ending_glyph),
|
||||||
|
word_range_width,
|
||||||
|
) in word_ranges
|
||||||
|
{
|
||||||
// To simplify the algorithm above, we might push empty ranges but we ignore them here
|
// To simplify the algorithm above, we might push empty ranges but we ignore them here
|
||||||
if ending_word == starting_word && starting_glyph == ending_glyph {
|
if ending_word == starting_word && starting_glyph == ending_glyph {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let fits = !if self.rtl {
|
let fits = !if self.rtl {
|
||||||
x - word_range_width < end_x
|
x - word_range_width < end_x
|
||||||
} else {
|
} else {
|
||||||
x + word_range_width > end_x
|
x + word_range_width > end_x
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if fits {
|
if fits {
|
||||||
current_visual_line.push((span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)));
|
current_visual_line.push((
|
||||||
|
span_index,
|
||||||
|
(starting_word, starting_glyph),
|
||||||
|
(ending_word, ending_glyph),
|
||||||
|
));
|
||||||
if self.rtl {
|
if self.rtl {
|
||||||
x -= word_range_width;
|
x -= word_range_width;
|
||||||
} else {
|
} else {
|
||||||
x += word_range_width;
|
x += word_range_width;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !current_visual_line.is_empty(){
|
if !current_visual_line.is_empty() {
|
||||||
vl_range_of_spans.push(current_visual_line);
|
vl_range_of_spans.push(current_visual_line);
|
||||||
current_visual_line = Vec::with_capacity(1);
|
current_visual_line = Vec::with_capacity(1);
|
||||||
x = start_x;
|
x = start_x;
|
||||||
}
|
}
|
||||||
current_visual_line.push((span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)));
|
current_visual_line.push((
|
||||||
|
span_index,
|
||||||
|
(starting_word, starting_glyph),
|
||||||
|
(ending_word, ending_glyph),
|
||||||
|
));
|
||||||
if self.rtl {
|
if self.rtl {
|
||||||
x -= word_range_width;
|
x -= word_range_width;
|
||||||
} else {
|
} else {
|
||||||
x += word_range_width;
|
x += word_range_width;
|
||||||
}
|
}
|
||||||
if word_range_width > line_width as f32 { // single word is bigger than line_width
|
if word_range_width > line_width as f32 {
|
||||||
|
// single word is bigger than line_width
|
||||||
vl_range_of_spans.push(current_visual_line);
|
vl_range_of_spans.push(current_visual_line);
|
||||||
current_visual_line = Vec::with_capacity(1);
|
current_visual_line = Vec::with_capacity(1);
|
||||||
x = start_x;
|
x = start_x;
|
||||||
|
|
@ -780,10 +789,18 @@ impl ShapeLine {
|
||||||
y = 0.;
|
y = 0.;
|
||||||
if self.rtl {
|
if self.rtl {
|
||||||
for range in new_order.iter().rev() {
|
for range in new_order.iter().rev() {
|
||||||
for (span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)) in visual_line[range.clone()].iter() {
|
for (
|
||||||
|
span_index,
|
||||||
|
(starting_word, starting_glyph),
|
||||||
|
(ending_word, ending_glyph),
|
||||||
|
) in visual_line[range.clone()].iter()
|
||||||
|
{
|
||||||
let span = &self.spans[*span_index];
|
let span = &self.spans[*span_index];
|
||||||
if starting_word == ending_word {
|
if starting_word == ending_word {
|
||||||
for glyph in span.words[*starting_word].glyphs[*starting_glyph..*ending_glyph].iter() {
|
for glyph in span.words[*starting_word].glyphs
|
||||||
|
[*starting_glyph..*ending_glyph]
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
let x_advance = font_size as f32 * glyph.x_advance;
|
let x_advance = font_size as f32 * glyph.x_advance;
|
||||||
let y_advance = font_size as f32 * glyph.y_advance;
|
let y_advance = font_size as f32 * glyph.y_advance;
|
||||||
if self.rtl {
|
if self.rtl {
|
||||||
|
|
@ -796,7 +813,7 @@ impl ShapeLine {
|
||||||
y += y_advance;
|
y += y_advance;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i in *starting_word..*ending_word+1 {
|
for i in *starting_word..*ending_word + 1 {
|
||||||
if let Some(word) = span.words.get(i) {
|
if let Some(word) = span.words.get(i) {
|
||||||
let (g1, g2) = if i == *starting_word {
|
let (g1, g2) = if i == *starting_word {
|
||||||
(*starting_glyph, word.glyphs.len())
|
(*starting_glyph, word.glyphs.len())
|
||||||
|
|
@ -825,10 +842,18 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for range in new_order {
|
for range in new_order {
|
||||||
for (span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)) in visual_line[range.clone()].iter() {
|
for (
|
||||||
|
span_index,
|
||||||
|
(starting_word, starting_glyph),
|
||||||
|
(ending_word, ending_glyph),
|
||||||
|
) in visual_line[range.clone()].iter()
|
||||||
|
{
|
||||||
let span = &self.spans[*span_index];
|
let span = &self.spans[*span_index];
|
||||||
if starting_word == ending_word {
|
if starting_word == ending_word {
|
||||||
for glyph in span.words[*starting_word].glyphs[*starting_glyph..*ending_glyph].iter() {
|
for glyph in span.words[*starting_word].glyphs
|
||||||
|
[*starting_glyph..*ending_glyph]
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
let x_advance = font_size as f32 * glyph.x_advance;
|
let x_advance = font_size as f32 * glyph.x_advance;
|
||||||
let y_advance = font_size as f32 * glyph.y_advance;
|
let y_advance = font_size as f32 * glyph.y_advance;
|
||||||
if self.rtl {
|
if self.rtl {
|
||||||
|
|
@ -841,7 +866,7 @@ impl ShapeLine {
|
||||||
y += y_advance;
|
y += y_advance;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i in *starting_word..*ending_word+1 {
|
for i in *starting_word..*ending_word + 1 {
|
||||||
if let Some(word) = span.words.get(i) {
|
if let Some(word) = span.words.get(i) {
|
||||||
let (g1, g2) = if i == *starting_word {
|
let (g1, g2) = if i == *starting_word {
|
||||||
(*starting_glyph, word.glyphs.len())
|
(*starting_glyph, word.glyphs.len())
|
||||||
|
|
@ -871,17 +896,18 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
let mut glyphs_swap = Vec::new();
|
let mut glyphs_swap = Vec::new();
|
||||||
mem::swap(&mut glyphs, &mut glyphs_swap);
|
mem::swap(&mut glyphs, &mut glyphs_swap);
|
||||||
layout_lines.push(
|
layout_lines.push(LayoutLine {
|
||||||
LayoutLine {
|
w: if self.rtl { start_x - x } else { x },
|
||||||
w: if self.rtl { start_x - x } else { x },
|
glyphs: glyphs_swap,
|
||||||
glyphs: glyphs_swap,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
push_line = false;
|
push_line = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if push_line {
|
if push_line {
|
||||||
layout_lines.push(LayoutLine { w: 0.0 , glyphs: Default::default() });
|
layout_lines.push(LayoutLine {
|
||||||
|
w: 0.0,
|
||||||
|
glyphs: Default::default(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
layout_lines
|
layout_lines
|
||||||
|
|
|
||||||
57
src/swash.rs
57
src/swash.rs
|
|
@ -6,22 +6,26 @@ use alloc::collections::BTreeMap as Map;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::collections::HashMap as Map;
|
use std::collections::HashMap as Map;
|
||||||
use swash::scale::{ScaleContext, image::Content};
|
use swash::scale::{image::Content, ScaleContext};
|
||||||
use swash::scale::{Render, Source, StrikeWith};
|
use swash::scale::{Render, Source, StrikeWith};
|
||||||
use swash::zeno::{Format, Vector};
|
use swash::zeno::{Format, Vector};
|
||||||
|
|
||||||
use crate::{CacheKey, Color, FontSystem};
|
use crate::{CacheKey, Color, FontSystem};
|
||||||
|
|
||||||
pub use swash::zeno::Command;
|
|
||||||
pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
|
pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
|
||||||
|
pub use swash::zeno::Command;
|
||||||
|
|
||||||
fn swash_image(font_system: &FontSystem, context: &mut ScaleContext, cache_key: CacheKey) -> Option<SwashImage> {
|
fn swash_image(
|
||||||
|
font_system: &FontSystem,
|
||||||
|
context: &mut ScaleContext,
|
||||||
|
cache_key: CacheKey,
|
||||||
|
) -> Option<SwashImage> {
|
||||||
let font = match font_system.get_font(cache_key.font_id) {
|
let font = match font_system.get_font(cache_key.font_id) {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => {
|
None => {
|
||||||
log::warn!("did not find font {:?}", cache_key.font_id);
|
log::warn!("did not find font {:?}", cache_key.font_id);
|
||||||
return None;
|
return None;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build the scaler
|
// Build the scaler
|
||||||
|
|
@ -33,9 +37,7 @@ fn swash_image(font_system: &FontSystem, context: &mut ScaleContext, cache_key:
|
||||||
|
|
||||||
// Compute the fractional offset-- you'll likely want to quantize this
|
// Compute the fractional offset-- you'll likely want to quantize this
|
||||||
// in a real renderer
|
// in a real renderer
|
||||||
let offset =
|
let offset = Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
|
||||||
Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
|
|
||||||
|
|
||||||
|
|
||||||
// Select our source order
|
// Select our source order
|
||||||
Render::new(&[
|
Render::new(&[
|
||||||
|
|
@ -54,7 +56,11 @@ fn swash_image(font_system: &FontSystem, context: &mut ScaleContext, cache_key:
|
||||||
.render(&mut scaler, cache_key.glyph_id)
|
.render(&mut scaler, cache_key.glyph_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swash_outline_commands(font_system: &FontSystem, context: &mut ScaleContext, cache_key: CacheKey) -> Option<Vec<swash::zeno::Command>> {
|
fn swash_outline_commands(
|
||||||
|
font_system: &FontSystem,
|
||||||
|
context: &mut ScaleContext,
|
||||||
|
cache_key: CacheKey,
|
||||||
|
) -> Option<Vec<swash::zeno::Command>> {
|
||||||
use swash::zeno::PathData as _;
|
use swash::zeno::PathData as _;
|
||||||
|
|
||||||
let font = match font_system.get_font(cache_key.font_id) {
|
let font = match font_system.get_font(cache_key.font_id) {
|
||||||
|
|
@ -62,10 +68,9 @@ fn swash_outline_commands(font_system: &FontSystem, context: &mut ScaleContext,
|
||||||
None => {
|
None => {
|
||||||
log::warn!("did not find font {:?}", cache_key.font_id);
|
log::warn!("did not find font {:?}", cache_key.font_id);
|
||||||
return None;
|
return None;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Build the scaler
|
// Build the scaler
|
||||||
let mut scaler = context
|
let mut scaler = context
|
||||||
.builder(font.as_swash())
|
.builder(font.as_swash())
|
||||||
|
|
@ -73,7 +78,9 @@ fn swash_outline_commands(font_system: &FontSystem, context: &mut ScaleContext,
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Scale the outline
|
// Scale the outline
|
||||||
let outline = scaler.scale_outline(cache_key.glyph_id).or_else(|| scaler.scale_color_outline(cache_key.glyph_id))?;
|
let outline = scaler
|
||||||
|
.scale_outline(cache_key.glyph_id)
|
||||||
|
.or_else(|| scaler.scale_color_outline(cache_key.glyph_id))?;
|
||||||
|
|
||||||
// Get the path information of the outline
|
// Get the path information of the outline
|
||||||
let path = outline.path();
|
let path = outline.path();
|
||||||
|
|
@ -97,7 +104,7 @@ impl<'a> SwashCache<'a> {
|
||||||
font_system,
|
font_system,
|
||||||
context: ScaleContext::new(),
|
context: ScaleContext::new(),
|
||||||
image_cache: Map::new(),
|
image_cache: Map::new(),
|
||||||
outline_command_cache: Map::new()
|
outline_command_cache: Map::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,15 +115,18 @@ impl<'a> SwashCache<'a> {
|
||||||
|
|
||||||
/// Create a swash Image from a cache key, caching results
|
/// Create a swash Image from a cache key, caching results
|
||||||
pub fn get_image(&mut self, cache_key: CacheKey) -> &Option<SwashImage> {
|
pub fn get_image(&mut self, cache_key: CacheKey) -> &Option<SwashImage> {
|
||||||
self.image_cache.entry(cache_key).or_insert_with(|| {
|
self.image_cache
|
||||||
swash_image(self.font_system, &mut self.context, cache_key)
|
.entry(cache_key)
|
||||||
})
|
.or_insert_with(|| swash_image(self.font_system, &mut self.context, cache_key))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_outline_commands(&mut self, cache_key: CacheKey) -> Option<&[swash::zeno::Command]> {
|
pub fn get_outline_commands(&mut self, cache_key: CacheKey) -> Option<&[swash::zeno::Command]> {
|
||||||
self.outline_command_cache.entry(cache_key).or_insert_with(|| {
|
self.outline_command_cache
|
||||||
swash_outline_commands(self.font_system, &mut self.context, cache_key)
|
.entry(cache_key)
|
||||||
}).as_deref()
|
.or_insert_with(|| {
|
||||||
|
swash_outline_commands(self.font_system, &mut self.context, cache_key)
|
||||||
|
})
|
||||||
|
.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerate pixels in an Image, use `with_image` for better performance
|
/// Enumerate pixels in an Image, use `with_image` for better performance
|
||||||
|
|
@ -124,7 +134,7 @@ impl<'a> SwashCache<'a> {
|
||||||
&mut self,
|
&mut self,
|
||||||
cache_key: CacheKey,
|
cache_key: CacheKey,
|
||||||
base: Color,
|
base: Color,
|
||||||
mut f: F
|
mut f: F,
|
||||||
) {
|
) {
|
||||||
if let Some(image) = self.get_image(cache_key) {
|
if let Some(image) = self.get_image(cache_key) {
|
||||||
let x = image.placement.left;
|
let x = image.placement.left;
|
||||||
|
|
@ -139,10 +149,7 @@ impl<'a> SwashCache<'a> {
|
||||||
f(
|
f(
|
||||||
x + off_x,
|
x + off_x,
|
||||||
y + off_y,
|
y + off_y,
|
||||||
Color(
|
Color(((image.data[i] as u32) << 24) | base.0 & 0xFF_FF_FF),
|
||||||
((image.data[i] as u32) << 24) |
|
|
||||||
base.0 & 0xFF_FF_FF
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -160,8 +167,8 @@ impl<'a> SwashCache<'a> {
|
||||||
image.data[i],
|
image.data[i],
|
||||||
image.data[i + 1],
|
image.data[i + 1],
|
||||||
image.data[i + 2],
|
image.data[i + 2],
|
||||||
image.data[i + 3]
|
image.data[i + 3],
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
i += 4;
|
i += 4;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue