Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions crates/kas-core/src/text/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@ pub use kas_text::format::FontToken;
pub struct Color(NonZeroU32);

impl Default for Color {
/// Use a theme-defined color (automatic)
fn default() -> Self {
Self::DEFAULT
}
}

impl Color {
/// Use the default theme-defined color
/// Use the theme-defined text color
///
/// As a foreground color, this maps to [`ColorsLinear::text`] or
/// [`ColorsLinear::text_invert`] depending on the background.
Expand All @@ -37,7 +36,7 @@ impl Color {
pub const DEFAULT: Self =
Color(NonZeroU32::new(u32::from_ne_bytes(Rgba8Srgb::rgba(1, 0, 0, 0).0)).unwrap());

/// Use the text-selection color
/// Use the theme-defined text selection color
///
/// As a foreground color this is identical to [`Self::DEFAULT`].
///
Expand Down Expand Up @@ -82,11 +81,9 @@ impl Color {

/// Resolve as (foreground) text color
#[inline]
pub fn resolve_foreground(self, theme: &ColorsLinear, bg: Option<Rgba>) -> Rgba {
pub fn resolve_foreground(self, theme: &ColorsLinear) -> Rgba {
if let Some(col) = self.as_rgba() {
col
} else if let Some(bg) = bg {
theme.text_over(bg)
} else {
theme.text
}
Expand Down Expand Up @@ -117,6 +114,20 @@ pub struct Colors {
pub background: Option<Color>,
}

impl Colors {
/// Resolve as (foreground) text color
#[inline]
pub fn resolve_foreground(self, theme: &ColorsLinear) -> Rgba {
if let Some(col) = self.foreground.as_rgba() {
col
} else if let Some(bg) = self.background {
theme.text_over(bg.resolve_background(theme))
} else {
theme.text
}
}
}

/// Decoration types
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand Down
6 changes: 3 additions & 3 deletions crates/kas-core/src/text/raster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ impl State {
.map(|e| e.1 == Default::default())
.unwrap_or(true)
{
let col = Color::default().resolve_foreground(theme, None);
let col = Color::default().resolve_foreground(theme);
self.text_with_color(allocator, queue, pass, pos, bb, text, col);
return;
}
Expand All @@ -575,7 +575,7 @@ impl State {
}
};

let col = token.foreground.resolve_foreground(theme, None);
let col = token.resolve_foreground(theme);
queue.push_sprite(pass, glyph.position.into(), bb, col, sprite);
};

Expand Down Expand Up @@ -634,7 +634,7 @@ impl State {
};

// Known limitation: this cannot depend on the background color.
let col = token.color.resolve_foreground(theme, None);
let col = token.color.resolve_foreground(theme);

match token.style {
LineStyle::Solid => draw_quad(quad, col),
Expand Down
41 changes: 15 additions & 26 deletions crates/kas-widgets/src/edit/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use kas::event::{
use kas::geom::{Rect, Vec2};
use kas::layout::{AlignHints, AxisInfo, SizeRules};
use kas::prelude::*;
use kas::text::format::Color;
use kas::text::{ConfiguredDisplay, CursorRange, NotReady, SelectionHelper, Status, format};
use kas::theme::{Background, DrawCx, SizeCx, TextClass};
use kas::util::UndoStack;
Expand All @@ -38,6 +37,7 @@ pub struct Editor {
id: Id,
read_only: bool,
display: ConfiguredDisplay,
highlight: highlight::Cache,
text: String,
colors: SchemeColors,
selection: SelectionHelper,
Expand All @@ -57,6 +57,7 @@ impl Default for Editor {
id: Id::default(),
read_only: false,
display: ConfiguredDisplay::new(TextClass::Editor, false),
highlight: Default::default(),
text: Default::default(),
colors: SchemeColors::default(),
selection: Default::default(),
Expand Down Expand Up @@ -100,8 +101,8 @@ impl<S: ToString> From<S> for Editor {
/// support scrolling of text content. Since this component is not a widget it
/// cannot implement [`Viewport`] directly, but it does provide the following
/// methods: [`Self::content_size`], [`Self::draw_with_offset`].
#[autoimpl(Debug where H: trait)]
pub struct Component<H: Highlighter>(pub Editor, highlight::Text<H>);
#[derive(Debug, Default)]
pub struct Component<H: Highlighter>(pub Editor, H);

impl<H: Highlighter> Deref for Component<H> {
type Target = ConfiguredDisplay;
Expand Down Expand Up @@ -142,30 +143,23 @@ impl<H: Highlighter> Layout for Component<H> {
}
}

impl<H: Default + Highlighter> Default for Component<H> {
#[inline]
fn default() -> Self {
Component(Editor::default(), Default::default())
}
}

impl<H: Default + Highlighter, S: ToString> From<S> for Component<H> {
impl<H: Highlighter + Default, S: ToString> From<S> for Component<H> {
#[inline]
fn from(text: S) -> Self {
Component(Editor::from(text), Default::default())
Component(Editor::from(text), H::default())
}
}

impl<H: Highlighter> Component<H> {
/// Replace the highlighter
#[inline]
pub fn with_highlighter<H2: Highlighter>(self, highlighter: H2) -> Component<H2> {
Component(self.0, highlight::Text::new(highlighter))
Component(self.0, highlighter)
}

/// Set a new highlighter of the same type
pub fn set_highlighter(&mut self, highlighter: H) {
self.1 = highlight::Text::new(highlighter);
self.1 = highlighter;
}

/// Get the background color
Expand Down Expand Up @@ -203,12 +197,6 @@ impl<H: Highlighter> Component<H> {
self.0.display.set_max_status(Status::New);
}
self.0.colors = self.1.scheme_colors();
if self.0.colors.selection_foreground == Color::default() {
self.0.colors.selection_foreground = Color::SELECTION;
}
if self.0.colors.selection_background == Color::default() {
self.0.colors.selection_background = Color::SELECTION;
}
self.0.display.configure(&mut cx.size_cx());

self.prepare(cx);
Expand All @@ -217,11 +205,12 @@ impl<H: Highlighter> Component<H> {
#[inline]
fn prepare_runs(&mut self) {
fn inner<H: Highlighter>(this: &mut Component<H>) {
this.1.highlight(&this.0.text);
this.0.highlight.highlight(&this.0.text, &mut this.1);
let (dpem, font) = (this.0.display.font_size(), this.0.display.font());
this.0
.display
.prepare_runs(this.0.text.as_str(), this.1.font_tokens(dpem, font));
this.0.display.prepare_runs(
this.0.text.as_str(),
this.0.highlight.font_tokens(dpem, font),
);
}

if self.0.display.status() < Status::LevelRuns {
Expand Down Expand Up @@ -314,7 +303,7 @@ impl<H: Highlighter> Component<H> {
let pos = self.rect().pos - offset;
let range: Range<u32> = self.0.selection.range().cast();

let color_tokens = self.1.color_tokens();
let color_tokens = self.0.highlight.color_tokens();
let default_colors = format::Colors {
foreground: self.0.colors.foreground,
background: None,
Expand Down Expand Up @@ -398,7 +387,7 @@ impl<H: Highlighter> Component<H> {
};
draw.text(pos, rect, display, tokens);

let decorations = self.1.decorations();
let decorations = self.0.highlight.decorations();
if !decorations.is_empty() {
draw.decorate_text(pos, rect, display, decorations);
}
Expand Down
44 changes: 26 additions & 18 deletions crates/kas-widgets/src/edit/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,40 @@

//! Supporting elements for syntax highlighting

mod cache;
#[cfg(feature = "syntect")] mod syntect;
mod text;

pub(crate) use cache::Cache;
use kas::impl_scope;
#[cfg(feature = "syntect")]
pub use syntect::{
SyntaxReference as SyntectSyntax, SyntaxSet as SyntectSyntaxSet, SyntectHighlighter,
};
pub(crate) use text::Text;

use kas::event::ConfigCx;
use kas::text::fonts::{FontStyle, FontWeight};
use kas::text::format::{Color, Colors, Decoration};

/// Colors provided by the highlighter's color scheme
///
/// Note that in each case [`Color::default()`] will resolve to the UI color
/// scheme's color for this property.
#[derive(Debug, Default)]
pub struct SchemeColors {
/// The default text color
pub foreground: Color,
/// The default background color
pub background: Color,
/// The color of the text cursor (sometimes called caret)
pub cursor: Color,
/// The color of selected text
pub selection_foreground: Color,
/// The background color of selected text
pub selection_background: Color,
impl_scope! {
/// Colors provided by the highlighter's color scheme
#[impl_default]
#[derive(Debug)]
pub struct SchemeColors {
/// The default text color
pub foreground: Color,
/// The default background color
pub background: Color,
/// The color of the text cursor (sometimes called caret)
pub cursor: Color,
/// The color of selected text
///
/// Note that the default value is [`Color::SELECTION`].
pub selection_foreground: Color = Color::SELECTION,
/// The background color of selected text
///
/// Note that the default value is [`Color::SELECTION`].
pub selection_background: Color = Color::SELECTION,
}
}

/// A highlighting token
Expand All @@ -47,6 +52,9 @@ pub struct SchemeColors {
#[non_exhaustive]
pub struct Token {
/// Text (foreground) and background color
///
/// The background color should be `None` unless highlighting is desired.
/// Specify the default background color using [`SchemeColors::background`].
pub colors: Colors,
/// Text weight (bold/medium/light)
pub weight: FontWeight,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,58 +18,27 @@ struct Fmt {
}

/// A highlighted text
///
/// Two `Text` objects compare equal if their formatted text is equal regardless
/// of the embedded highlighter.
#[derive(Clone, Debug)]
#[kas::autoimpl(PartialEq ignore self.highlighter)]
pub(crate) struct Text<H: Highlighter> {
highlighter: H,
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Cache {
fonts: Vec<Fmt>,
colors: Vec<(u32, Colors)>,
decorations: Vec<(u32, Decoration)>,
}

impl<H: Highlighter + Default> Default for Text<H> {
fn default() -> Self {
Self::new(H::default())
}
}

impl<H: Highlighter> Text<H> {
/// Construct a new instance
impl Default for Cache {
#[inline]
pub fn new(highlighter: H) -> Self {
Text {
highlighter,
fn default() -> Self {
Cache {
fonts: vec![Fmt::default()],
colors: vec![],
decorations: vec![],
}
}
}

/// Configure the highlighter
///
/// This is called when the widget is configured. It may be used to set the
/// theme / color scheme.
///
/// Returns `true` when the highlighter must be re-run.
#[inline]
#[must_use]
pub fn configure(&mut self, cx: &mut ConfigCx) -> bool {
self.highlighter.configure(cx)
}

/// Get scheme colors
///
/// This method allows usage of the highlighter's colors by the editor.
#[inline]
pub fn scheme_colors(&self) -> SchemeColors {
self.highlighter.scheme_colors()
}

impl Cache {
/// Highlight the text (from scratch)
pub fn highlight(&mut self, text: &str) {
pub fn highlight<H: Highlighter>(&mut self, text: &str, highlighter: &mut H) {
self.fonts.clear();
self.fonts.push(Fmt::default());
self.colors.clear();
Expand Down Expand Up @@ -110,7 +79,7 @@ impl<H: Highlighter> Text<H> {
state = token;
};

if let Err(err) = self.highlighter.highlight_text(text, &mut push_token) {
if let Err(err) = highlighter.highlight_text(text, &mut push_token) {
log::error!("Highlighting failed: {err}");
debug_assert!(false, "Highlighter: {err}");
}
Expand Down
Loading