diff --git a/README.md b/README.md
index 92451af7f..10e9c94bf 100644
--- a/README.md
+++ b/README.md
@@ -56,14 +56,13 @@ Installation and dependencies
#### Rust
-KAS requires [Rust] version 1.52 or greater (currently in **beta**: *usually*
-we maintain compatibility with the latest stable release).
-Using the **nightly** channel does have a couple of advantages:
+KAS requires a recent [Rust] compiler. Currently, version 1.52 or greater is
+required. Using the **nightly** channel does have a few advantages:
- Proceedural macros emit better diagnostics. In some cases, diagnostics are
missed without nightly rustc, hence **nightly is recommended for development**.
-- Documentation generated via `cargo doc` requires nightly for links
-- A few minor option things: see [Feature flags](#feature-flags) below.
+- The `nightly` (`min_spec`) feature allows some visual improvements (see below).
+- The `doc_cfg` feature may be used for API docs.
### Quick-start
@@ -137,8 +136,9 @@ The `kas` crate has the following feature flags:
Additionally, the following flags require a nightly compiler:
- `nightly`: enables "more stable" unstable features
-- `min_spec` (enabled by `nightly`): use `min_specialization` to draw
- underlines in `AccelLabel`
+- `min_spec` (enabled by `nightly`): use `min_specialization` for some visual
+ improvements: scrolled regions are drawn under scrollbars,
+ underlines on checkbox accelerator keys show with the Alt key.
- `spec`: use `specialization` to enable `TryFormat`
- `gat`: compatibility with `kas-text/gat`
diff --git a/crates/kas-core/src/draw/color.rs b/crates/kas-core/src/draw/color.rs
index 00fc0b5a8..e24d46abe 100644
--- a/crates/kas-core/src/draw/color.rs
+++ b/crates/kas-core/src/draw/color.rs
@@ -57,9 +57,14 @@ impl Rgba {
Self::rgba(s, s, s, a)
}
+ /// Get the sum of the three colour components
+ pub fn sum(self) -> f32 {
+ self.r + self.g + self.b
+ }
+
/// Average three colour components (desaturate)
pub fn average(self) -> Self {
- Self::ga((self.r + self.g + self.b) * (1.0 / 3.0), self.a)
+ Self::ga(self.sum() * (1.0 / 3.0), self.a)
}
/// Multiply and clamp three colour components
@@ -131,9 +136,14 @@ impl Rgb {
Self::rgb(s, s, s)
}
+ /// Get the sum of the three colour components
+ pub fn sum(self) -> f32 {
+ self.r + self.g + self.b
+ }
+
/// Average three colour components (desaturate)
pub fn average(self) -> Self {
- Self::grey((self.r + self.g + self.b) * (1.0 / 3.0))
+ Self::grey(self.sum() * (1.0 / 3.0))
}
/// Multiply and clamp three colour components
diff --git a/crates/kas-core/src/draw/handle.rs b/crates/kas-core/src/draw/handle.rs
index f6951240a..afd787d5d 100644
--- a/crates/kas-core/src/draw/handle.rs
+++ b/crates/kas-core/src/draw/handle.rs
@@ -138,6 +138,8 @@ pub trait SizeHandle {
fn pixels_from_em(&self, em: f32) -> f32;
/// Size of a frame around child widget(s)
+ ///
+ /// This already includes the margins specified by [`Self::frame_margins`].
fn frame(&self, vert: bool) -> FrameRules;
/// Frame/margin around a menu entry
@@ -160,6 +162,9 @@ pub trait SizeHandle {
/// Widgets must not draw in outer margins.
fn outer_margins(&self) -> Margins;
+ /// The margin around frames and separators
+ fn frame_margins(&self) -> Margins;
+
/// The margin around text elements
///
/// Similar to [`Self::outer_margins`], but intended for things like text
@@ -467,6 +472,9 @@ impl> SizeHandle for R {
fn outer_margins(&self) -> Margins {
self.deref().outer_margins()
}
+ fn frame_margins(&self) -> Margins {
+ self.deref().frame_margins()
+ }
fn text_margins(&self) -> Margins {
self.deref().text_margins()
}
diff --git a/crates/kas-core/src/geom/vector.rs b/crates/kas-core/src/geom/vector.rs
index 7bdeefe7c..82cd05ffb 100644
--- a/crates/kas-core/src/geom/vector.rs
+++ b/crates/kas-core/src/geom/vector.rs
@@ -146,6 +146,9 @@ macro_rules! impl_vec2 {
/// Zero
pub const ZERO: $T = $T::splat(0.0);
+ /// One
+ pub const ONE: $T = $T::splat(1.0);
+
/// Positive infinity
pub const INFINITY: $T = $T::splat(<$f>::INFINITY);
@@ -335,6 +338,14 @@ macro_rules! impl_vec2 {
}
}
+ impl Div<$T> for $f {
+ type Output = $T;
+ #[inline]
+ fn div(self, rhs: $T) -> Self::Output {
+ $T(self / rhs.0, self / rhs.1)
+ }
+ }
+
impl From<($f, $f)> for $T {
#[inline]
fn from(arg: ($f, $f)) -> Self {
diff --git a/crates/kas-theme/src/colors.rs b/crates/kas-theme/src/colors.rs
index 9e7603032..b73fbecb0 100644
--- a/crates/kas-theme/src/colors.rs
+++ b/crates/kas-theme/src/colors.rs
@@ -6,7 +6,8 @@
//! Colour schemes
use kas::draw::color::{Rgba, Rgba8Srgb};
-use kas::draw::{InputState, TextClass};
+use kas::draw::InputState;
+use std::str::FromStr;
const MULT_DEPRESS: f32 = 0.75;
const MULT_HIGHLIGHT: f32 = 1.25;
@@ -24,22 +25,30 @@ pub struct Colors {
pub edit_bg: C,
/// Background colour of `EditBox` (error state)
pub edit_bg_error: C,
- /// Text colour in an `EditBox`
+ /// Theme accent
+ ///
+ /// This should be a bold colour, used for small details.
+ pub accent: C,
+ /// Soft version of accent
+ ///
+ /// A softer version of the accent colour, used for block elements in some themes.
+ pub accent_soft: C,
+ /// Highlight colour for keyboard navigation
+ ///
+ /// This may be the same as `accent`. It should contrast well with
+ /// `accent_soft`. Themes should use `nav_focus` over `accent` where a
+ /// strong contrast is required.
+ pub nav_focus: C,
+ /// Normal text colour (over background)
pub text: C,
- /// Selected tect colour
- pub text_sel: C,
+ /// Opposing text colour (e.g. white if `text` is black)
+ pub text_invert: C,
+ /// Disabled text colour
+ pub text_disabled: C,
/// Selected text background colour
+ ///
+ /// This may be the same as `accent_soft`.
pub text_sel_bg: C,
- /// Text colour in a `Label`
- pub label_text: C,
- /// Text colour on a `TextButton`
- pub button_text: C,
- /// Highlight colour for keyboard navigation
- pub nav_focus: C,
- /// Colour of a `TextButton`
- pub button: C,
- /// Colour of mark within a `CheckBox` or `RadioBox`
- pub checkbox: C,
}
/// [`Colors`] parameterised for reading and writing using sRGB
@@ -53,16 +62,15 @@ impl From for ColorsLinear {
Colors {
background: col.background.into(),
frame: col.frame.into(),
+ accent: col.accent.into(),
+ accent_soft: col.accent_soft.into(),
+ nav_focus: col.nav_focus.into(),
edit_bg: col.edit_bg.into(),
edit_bg_error: col.edit_bg_error.into(),
text: col.text.into(),
- text_sel: col.text_sel.into(),
+ text_invert: col.text_invert.into(),
+ text_disabled: col.text_disabled.into(),
text_sel_bg: col.text_sel_bg.into(),
- label_text: col.label_text.into(),
- button_text: col.button_text.into(),
- nav_focus: col.nav_focus.into(),
- button: col.button.into(),
- checkbox: col.checkbox.into(),
}
}
}
@@ -72,16 +80,15 @@ impl From for ColorsSrgb {
Colors {
background: col.background.into(),
frame: col.frame.into(),
+ accent: col.accent.into(),
+ accent_soft: col.accent_soft.into(),
+ nav_focus: col.nav_focus.into(),
edit_bg: col.edit_bg.into(),
edit_bg_error: col.edit_bg_error.into(),
text: col.text.into(),
- text_sel: col.text_sel.into(),
+ text_invert: col.text_invert.into(),
+ text_disabled: col.text_disabled.into(),
text_sel_bg: col.text_sel_bg.into(),
- label_text: col.label_text.into(),
- button_text: col.button_text.into(),
- nav_focus: col.nav_focus.into(),
- button: col.button.into(),
- checkbox: col.checkbox.into(),
}
}
}
@@ -89,81 +96,78 @@ impl From for ColorsSrgb {
impl Default for ColorsLinear {
#[inline]
fn default() -> Self {
- Colors::white_blue()
+ ColorsSrgb::default().into()
}
}
impl Default for ColorsSrgb {
#[inline]
fn default() -> Self {
- ColorsLinear::default().into()
+ ColorsSrgb::light()
}
}
-// NOTE: these colour schemes are defined using linear (Rgba) colours instead of
-// sRGB (Rgba8Srgb) colours for historical reasons. Either should be fine.
-impl ColorsLinear {
- /// White background with blue activable items
- pub fn white_blue() -> Self {
+impl ColorsSrgb {
+ /// Default "light" scheme
+ pub fn light() -> Self {
Colors {
- background: Rgba::grey(1.0),
- frame: Rgba::grey(0.7),
- edit_bg: Rgba::grey(1.0),
- edit_bg_error: Rgba::rgb(1.0, 0.5, 0.5),
- text: Rgba::grey(0.0),
- text_sel: Rgba::grey(1.0),
- text_sel_bg: Rgba::rgb(0.15, 0.525, 0.75),
- label_text: Rgba::grey(0.0),
- button_text: Rgba::grey(1.0),
- nav_focus: Rgba::rgb(0.9, 0.65, 0.4),
- button: Rgba::rgb(0.2, 0.7, 1.0),
- checkbox: Rgba::rgb(0.2, 0.7, 1.0),
+ background: Rgba8Srgb::from_str("#FAFAFA").unwrap(),
+ frame: Rgba8Srgb::from_str("#BCBCBC").unwrap(),
+ accent: Rgba8Srgb::from_str("#7E3FF2").unwrap(),
+ accent_soft: Rgba8Srgb::from_str("#B38DF9").unwrap(),
+ nav_focus: Rgba8Srgb::from_str("#7E3FF2").unwrap(),
+ edit_bg: Rgba8Srgb::from_str("#FAFAFA").unwrap(),
+ edit_bg_error: Rgba8Srgb::from_str("#FFBCBC").unwrap(),
+ text: Rgba8Srgb::from_str("#000000").unwrap(),
+ text_invert: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
+ text_disabled: Rgba8Srgb::from_str("#AAAAAA").unwrap(),
+ text_sel_bg: Rgba8Srgb::from_str("#A172FA").unwrap(),
}
}
- /// Light scheme
- pub fn light() -> Self {
+ /// Dark scheme
+ pub fn dark() -> Self {
Colors {
- background: Rgba::grey(0.9),
- frame: Rgba::rgb(0.8, 0.8, 0.9),
- edit_bg: Rgba::grey(1.0),
- edit_bg_error: Rgba::rgb(1.0, 0.5, 0.5),
- text: Rgba::grey(0.0),
- text_sel: Rgba::grey(0.0),
- text_sel_bg: Rgba::rgb(0.8, 0.72, 0.24),
- label_text: Rgba::grey(0.0),
- button_text: Rgba::grey(0.0),
- nav_focus: Rgba::rgb(0.9, 0.65, 0.4),
- button: Rgba::rgb(1.0, 0.9, 0.3),
- checkbox: Rgba::grey(0.4),
+ background: Rgba8Srgb::from_str("#404040").unwrap(),
+ frame: Rgba8Srgb::from_str("#AAAAAA").unwrap(),
+ accent: Rgba8Srgb::from_str("#F74C00").unwrap(),
+ accent_soft: Rgba8Srgb::from_str("#E77346").unwrap(),
+ nav_focus: Rgba8Srgb::from_str("#A33100").unwrap(),
+ edit_bg: Rgba8Srgb::from_str("#595959").unwrap(),
+ edit_bg_error: Rgba8Srgb::from_str("#FFBCBC").unwrap(),
+ text: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
+ text_invert: Rgba8Srgb::from_str("#000000").unwrap(),
+ text_disabled: Rgba8Srgb::from_str("#CBCBCB").unwrap(),
+ text_sel_bg: Rgba8Srgb::from_str("#E77346").unwrap(),
}
}
- /// Dark scheme
- pub fn dark() -> Self {
+ /// Blue scheme
+ pub fn blue() -> Self {
Colors {
- background: Rgba::grey(0.2),
- frame: Rgba::grey(0.4),
- edit_bg: Rgba::grey(0.1),
- edit_bg_error: Rgba::rgb(1.0, 0.5, 0.5),
- text: Rgba::grey(1.0),
- text_sel: Rgba::grey(1.0),
- text_sel_bg: Rgba::rgb(0.6, 0.3, 0.1),
- label_text: Rgba::grey(1.0),
- button_text: Rgba::grey(1.0),
- nav_focus: Rgba::rgb(1.0, 0.7, 0.5),
- button: Rgba::rgb(0.5, 0.1, 0.1),
- checkbox: Rgba::rgb(0.5, 0.1, 0.1),
+ background: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
+ frame: Rgba8Srgb::from_str("#DADADA").unwrap(),
+ accent: Rgba8Srgb::from_str("#7CDAFF").unwrap(),
+ accent_soft: Rgba8Srgb::from_str("#7CDAFF").unwrap(),
+ nav_focus: Rgba8Srgb::from_str("#3B697A").unwrap(),
+ edit_bg: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
+ edit_bg_error: Rgba8Srgb::from_str("#FFBCBC").unwrap(),
+ text: Rgba8Srgb::from_str("#000000").unwrap(),
+ text_invert: Rgba8Srgb::from_str("#FFFFFF").unwrap(),
+ text_disabled: Rgba8Srgb::from_str("#AAAAAA").unwrap(),
+ text_sel_bg: Rgba8Srgb::from_str("#6CC0E1").unwrap(),
}
}
+}
+impl ColorsLinear {
/// Adjust a colour depending on state
pub fn adjust_for_state(col: Rgba, state: InputState) -> Rgba {
if state.disabled {
col.average()
} else if state.depress {
col.multiply(MULT_DEPRESS)
- } else if state.hover {
+ } else if state.hover || state.char_focus {
col.multiply(MULT_HIGHLIGHT).max(MIN_HIGHLIGHT)
} else {
col
@@ -190,18 +194,24 @@ impl ColorsLinear {
}
}
- /// Get colour for a button, depending on state
+ /// Get accent colour, adjusted for state
+ #[inline]
+ pub fn accent_state(&self, state: InputState) -> Rgba {
+ Self::adjust_for_state(self.accent, state)
+ }
+
+ /// Get soft accent colour, adjusted for state
#[inline]
- pub fn button_state(&self, state: InputState) -> Rgba {
- Self::adjust_for_state(self.button, state)
+ pub fn accent_soft_state(&self, state: InputState) -> Rgba {
+ Self::adjust_for_state(self.accent_soft, state)
}
/// Get colour for a checkbox mark, depending on state
pub fn check_mark_state(&self, state: InputState, checked: bool) -> Option {
if checked {
- Some(Self::adjust_for_state(self.checkbox, state))
+ Some(Self::adjust_for_state(self.accent, state))
} else if state.depress {
- Some(self.checkbox.multiply(MULT_DEPRESS))
+ Some(self.accent.multiply(MULT_DEPRESS))
} else {
None
}
@@ -210,24 +220,19 @@ impl ColorsLinear {
/// Get background highlight colour of a menu entry, if any
pub fn menu_entry(&self, state: InputState) -> Option {
if state.depress || state.nav_focus {
- Some(self.button.multiply(MULT_DEPRESS))
+ Some(self.accent_soft.multiply(MULT_DEPRESS))
} else {
None
}
}
- /// Get colour of a scrollbar, depending on state
- #[inline]
- pub fn scrollbar_state(&self, state: InputState) -> Rgba {
- self.button_state(state)
- }
-
- /// Get text colour from class
- pub fn text_class(&self, class: TextClass) -> Rgba {
- match class {
- TextClass::Label | TextClass::MenuLabel | TextClass::LabelScroll => self.label_text,
- TextClass::Button => self.button_text,
- TextClass::Edit | TextClass::EditMulti => self.text,
+ /// Get appropriate text colour over the given background
+ pub fn text_over(&self, bg: Rgba) -> Rgba {
+ let bg_sum = bg.sum();
+ if (bg_sum - self.text_invert.sum()).abs() > (bg_sum - self.text.sum()).abs() {
+ self.text_invert
+ } else {
+ self.text
}
}
}
diff --git a/crates/kas-theme/src/config.rs b/crates/kas-theme/src/config.rs
index 838e8ae22..c6dc36411 100644
--- a/crates/kas-theme/src/config.rs
+++ b/crates/kas-theme/src/config.rs
@@ -5,7 +5,7 @@
//! Theme configuration
-use crate::{ColorsLinear, ColorsSrgb, ThemeConfig};
+use crate::{ColorsSrgb, ThemeConfig};
use kas::draw::TextClass;
use kas::text::fonts::{fonts, AddMode, FontSelector};
use kas::TkAction;
@@ -236,9 +236,9 @@ mod defaults {
pub fn color_schemes() -> BTreeMap {
let mut schemes = BTreeMap::new();
- schemes.insert("".to_string(), ColorsLinear::white_blue().into());
- schemes.insert("light".to_string(), ColorsLinear::light().into());
- schemes.insert("dark".to_string(), ColorsLinear::dark().into());
+ schemes.insert("light".to_string(), ColorsSrgb::light());
+ schemes.insert("dark".to_string(), ColorsSrgb::dark());
+ schemes.insert("blue".to_string(), ColorsSrgb::blue());
schemes
}
diff --git a/crates/kas-theme/src/dim.rs b/crates/kas-theme/src/dim.rs
index 4d9fcaf1a..bc817e46c 100644
--- a/crates/kas-theme/src/dim.rs
+++ b/crates/kas-theme/src/dim.rs
@@ -26,12 +26,16 @@ pub struct Parameters {
pub outer_margin: f32,
/// Margin inside a frame before contents
pub inner_margin: f32,
+ /// Margin around frames and seperators
+ pub frame_margin: f32,
/// Margin between text elements
pub text_margin: f32,
/// Frame size
pub frame_size: f32,
/// Button frame size (non-flat outer region)
pub button_frame: f32,
+ /// Checkbox inner size in Points
+ pub checkbox_inner: f32,
/// Scrollbar minimum handle size
pub scrollbar_size: Vec2,
/// Slider minimum handle size
@@ -51,6 +55,7 @@ pub struct Dimensions {
pub min_line_length: i32,
pub outer_margin: u16,
pub inner_margin: u16,
+ pub frame_margin: u16,
pub text_margin: u16,
pub frame: i32,
pub button_frame: i32,
@@ -73,6 +78,7 @@ impl Dimensions {
let outer_margin = (params.outer_margin * scale_factor).cast_nearest();
let inner_margin = (params.inner_margin * scale_factor).cast_nearest();
+ let frame_margin = (params.frame_margin * scale_factor).cast_nearest();
let text_margin = (params.text_margin * scale_factor).cast_nearest();
let frame = (params.frame_size * scale_factor).cast_nearest();
Dimensions {
@@ -84,10 +90,12 @@ impl Dimensions {
min_line_length: (8.0 * dpem).cast_nearest(),
outer_margin,
inner_margin,
+ frame_margin,
text_margin,
frame,
button_frame: (params.button_frame * scale_factor).cast_nearest(),
- checkbox: i32::conv_nearest(9.0 * dpp) + 2 * (i32::from(inner_margin) + frame),
+ checkbox: i32::conv_nearest(params.checkbox_inner * dpp)
+ + 2 * (i32::from(inner_margin) + frame),
scrollbar: Size::from(params.scrollbar_size * scale_factor),
slider: Size::from(params.slider_size * scale_factor),
progress_bar: Size::from(params.progress_bar * scale_factor),
@@ -143,7 +151,7 @@ impl SizeHandle for Window {
}
fn frame(&self, _vert: bool) -> FrameRules {
- FrameRules::new_sym(self.dims.frame, 0, 0)
+ FrameRules::new_sym(self.dims.frame, 0, self.dims.frame_margin)
}
fn menu_frame(&self, vert: bool) -> FrameRules {
let mut size = self.dims.frame;
@@ -168,6 +176,10 @@ impl SizeHandle for Window {
Margins::splat(self.dims.outer_margin)
}
+ fn frame_margins(&self) -> Margins {
+ Margins::splat(self.dims.frame_margin)
+ }
+
fn text_margins(&self) -> Margins {
Margins::splat(self.dims.text_margin)
}
diff --git a/crates/kas-theme/src/flat_theme.rs b/crates/kas-theme/src/flat_theme.rs
index 40e0d6f05..be7f72b96 100644
--- a/crates/kas-theme/src/flat_theme.rs
+++ b/crates/kas-theme/src/flat_theme.rs
@@ -75,12 +75,15 @@ impl FlatTheme {
const DIMS: dim::Parameters = dim::Parameters {
outer_margin: 8.0,
inner_margin: 1.2,
+ frame_margin: 2.4,
text_margin: 2.0,
- frame_size: 4.0,
- button_frame: 6.0,
+ frame_size: 2.4,
+ // NOTE: visual thickness is (button_frame * scale_factor).round() * (1 - BG_SHRINK_FACTOR)
+ button_frame: 2.4,
+ checkbox_inner: 5.0,
scrollbar_size: Vec2::splat(8.0),
- slider_size: Vec2(12.0, 25.0),
- progress_bar: Vec2::splat(12.0),
+ slider_size: Vec2(16.0, 16.0),
+ progress_bar: Vec2::splat(8.0),
};
pub struct DrawHandle<'a, DS: DrawSharedImpl> {
@@ -200,50 +203,6 @@ impl ThemeApi for FlatTheme {
}
}
-impl<'a, DS: DrawSharedImpl> DrawHandle<'a, DS>
-where
- DS::Draw: DrawRoundedImpl,
-{
- /// Draw an edit box with optional navigation highlight.
- /// Return the inner rect.
- ///
- /// - `outer`: define position via outer rect
- /// - `bg_col`: colour of background
- /// - `nav_col`: colour of navigation highlight, if visible
- fn draw_edit_box(&mut self, outer: Rect, bg_col: Rgba, nav_col: Option) -> Quad {
- let outer = Quad::from(outer);
- let inner1 = outer.shrink(self.window.dims.frame as f32 * BG_SHRINK_FACTOR);
- let inner2 = outer.shrink(self.window.dims.frame as f32);
-
- self.draw.rect(inner1, bg_col);
-
- // We draw over the inner rect, taking advantage of the fact that
- // rounded frames get drawn after flat rects.
- self.draw
- .rounded_frame(outer, inner2, BG_SHRINK_FACTOR, self.cols.frame);
-
- if let Some(col) = nav_col {
- self.draw.rounded_frame(inner1, inner2, 0.6, col);
- }
-
- inner2
- }
-
- /// Draw a handle (for slider, scrollbar)
- fn draw_handle(&mut self, rect: Rect, state: InputState) {
- let outer = Quad::from(rect);
- let thickness = outer.size().min_comp() / 2.0;
- let inner = outer.shrink(thickness);
- let col = self.cols.scrollbar_state(state);
- self.draw.rounded_frame(outer, inner, 0.0, col);
-
- if let Some(col) = self.cols.nav_region(state) {
- let outer = outer.shrink(thickness / 4.0);
- self.draw.rounded_frame(outer, inner, 0.6, col);
- }
- }
-}
-
impl<'a, DS: DrawSharedImpl> draw::DrawHandle for DrawHandle<'a, DS>
where
DS::Draw: DrawRoundedImpl,
@@ -319,24 +278,24 @@ where
self.draw.frame(outer, inner, col);
}
- fn text(&mut self, pos: Coord, text: &TextDisplay, class: TextClass) {
+ fn text(&mut self, pos: Coord, text: &TextDisplay, _: TextClass) {
let pos = pos;
- let col = self.cols.text_class(class);
+ let col = self.cols.text;
self.draw.text(pos.into(), text, col);
}
- fn text_effects(&mut self, pos: Coord, text: &dyn TextApi, class: TextClass) {
+ fn text_effects(&mut self, pos: Coord, text: &dyn TextApi, _: TextClass) {
self.draw.text_col_effects(
(pos).into(),
text.display(),
- self.cols.text_class(class),
+ self.cols.text,
text.effect_tokens(),
);
}
- fn text_accel(&mut self, pos: Coord, text: &Text, state: bool, class: TextClass) {
+ fn text_accel(&mut self, pos: Coord, text: &Text, state: bool, _: TextClass) {
let pos = Vec2::from(pos);
- let col = self.cols.text_class(class);
+ let col = self.cols.text;
if state {
let effects = text.text().effect_tokens();
self.draw.text_col_effects(pos, text.as_ref(), col, effects);
@@ -350,10 +309,11 @@ where
pos: Coord,
text: &TextDisplay,
range: Range,
- class: TextClass,
+ _: TextClass,
) {
let pos = Vec2::from(pos);
- let col = self.cols.text_class(class);
+ let col = self.cols.text;
+ let sel_col = self.cols.text_over(self.cols.text_sel_bg);
// Draw background:
for (p1, p2) in &text.highlight_lines(range.clone()) {
@@ -372,7 +332,7 @@ where
Effect {
start: range.start.cast(),
flags: Default::default(),
- aux: self.cols.text_sel,
+ aux: sel_col,
},
Effect {
start: range.end.cast(),
@@ -383,11 +343,11 @@ where
self.draw.text_effects(pos, text, &effects);
}
- fn edit_marker(&mut self, pos: Coord, text: &TextDisplay, class: TextClass, byte: usize) {
+ fn edit_marker(&mut self, pos: Coord, text: &TextDisplay, _: TextClass, byte: usize) {
let width = self.window.dims.font_marker_width;
let pos = Vec2::from(pos);
- let mut col = self.cols.text_class(class);
+ let mut col = self.cols.nav_focus;
for cursor in text.text_glyph_pos(byte).rev() {
let mut p1 = pos + Vec2::from(cursor.pos);
let mut p2 = p1;
@@ -421,47 +381,86 @@ where
fn button(&mut self, rect: Rect, col: Option, state: InputState) {
let outer = Quad::from(rect);
- let col = col.map(|c| c.into()).unwrap_or(self.cols.button);
+
+ let col = if state.nav_focus && !state.disabled {
+ self.cols.accent_soft
+ } else {
+ col.map(|c| c.into()).unwrap_or(self.cols.background)
+ };
let col = ColorsLinear::adjust_for_state(col, state);
+ if col != self.cols.background {
+ let inner = outer.shrink(self.window.dims.button_frame as f32 * BG_SHRINK_FACTOR);
+ self.draw.rect(inner, col);
+ }
+ let col = self.cols.nav_region(state).unwrap_or(self.cols.frame);
let inner = outer.shrink(self.window.dims.button_frame as f32);
- self.draw.rounded_frame(outer, inner, 0.0, col);
- self.draw.rect(inner, col);
+ self.draw.rounded_frame(outer, inner, BG_SHRINK_FACTOR, col);
+ }
- if let Some(col) = self.cols.nav_region(state) {
- let outer = outer.shrink(self.window.dims.inner_margin as f32);
- self.draw.rounded_frame(outer, inner, 0.6, col);
+ fn edit_box(&mut self, rect: Rect, mut state: InputState) {
+ let outer = Quad::from(rect);
+
+ state.depress = false;
+ let col = match state.error {
+ true => self.cols.edit_bg_error,
+ false => self.cols.edit_bg,
+ };
+ let col = ColorsLinear::adjust_for_state(col, state);
+ if col != self.cols.background {
+ let inner = outer.shrink(self.window.dims.button_frame as f32 * BG_SHRINK_FACTOR);
+ self.draw.rect(inner, col);
}
- }
- fn edit_box(&mut self, rect: Rect, state: InputState) {
- let bg_col = self.cols.bg_col(state);
- self.draw_edit_box(rect, bg_col, self.cols.nav_region(state));
+ let inner = outer.shrink(self.window.dims.button_frame as f32);
+ self.draw
+ .rounded_frame(outer, inner, BG_SHRINK_FACTOR, self.cols.frame);
+
+ if state.nav_focus {
+ let r = 0.5 * self.window.dims.button_frame as f32;
+ let y = outer.b.1 - r;
+ let a = Vec2(outer.a.0 + r, y);
+ let b = Vec2(outer.b.0 - r, y);
+ self.draw.rounded_line(a, b, r, self.cols.nav_focus);
+ }
}
fn checkbox(&mut self, rect: Rect, checked: bool, state: InputState) {
- let bg_col = self.cols.bg_col(state);
- let nav_col = self.cols.nav_region(state).or(Some(bg_col));
+ let outer = Quad::from(rect);
- let inner = self.draw_edit_box(rect, bg_col, nav_col);
+ let col = ColorsLinear::adjust_for_state(self.cols.background, state);
+ if col != self.cols.background {
+ let inner = outer.shrink(self.window.dims.button_frame as f32 * BG_SHRINK_FACTOR);
+ self.draw.rect(inner, col);
+ }
+
+ let col = self.cols.nav_region(state).unwrap_or(self.cols.frame);
+ let inner = outer.shrink(self.window.dims.button_frame as f32);
+ self.draw.rounded_frame(outer, inner, BG_SHRINK_FACTOR, col);
if let Some(col) = self.cols.check_mark_state(state, checked) {
- let radius = inner.size().sum() * (1.0 / 16.0);
- let inner = inner.shrink(self.window.dims.inner_margin as f32 + radius);
- self.draw.rounded_line(inner.a, inner.b, radius, col);
- self.draw.rounded_line(inner.ab(), inner.ba(), radius, col);
+ let inner = inner.shrink((2 * self.window.dims.inner_margin) as f32);
+ self.draw.rect(inner, col);
}
}
fn radiobox(&mut self, rect: Rect, checked: bool, state: InputState) {
- let bg_col = self.cols.bg_col(state);
- let nav_col = self.cols.nav_region(state).or(Some(bg_col));
+ let outer = Quad::from(rect);
- let inner = self.draw_edit_box(rect, bg_col, nav_col);
+ let col = ColorsLinear::adjust_for_state(self.cols.background, state);
+ if col != self.cols.background {
+ self.draw.circle(outer, 0.0, col);
+ }
+
+ let col = self.cols.nav_region(state).unwrap_or(self.cols.frame);
+ const F: f32 = 2.0 * (1.0 - BG_SHRINK_FACTOR); // match checkbox frame
+ let r = 1.0 - F * self.window.dims.button_frame as f32 / rect.size.0 as f32;
+ self.draw.circle(outer, r, col);
if let Some(col) = self.cols.check_mark_state(state, checked) {
- let inner = inner.shrink(self.window.dims.inner_margin as f32);
- self.draw.circle(inner, 0.5, col);
+ let r = self.window.dims.button_frame + 2 * self.window.dims.inner_margin as i32;
+ let inner = outer.shrink(r as f32);
+ self.draw.circle(inner, 0.0, col);
}
}
@@ -469,41 +468,73 @@ where
// track
let outer = Quad::from(rect);
let inner = outer.shrink(outer.size().min_comp() / 2.0);
- let col = self.cols.frame;
+ let mut col = self.cols.frame;
+ col.a = 0.5; // HACK
self.draw.rounded_frame(outer, inner, 0.0, col);
// handle
- self.draw_handle(h_rect, state);
+ let outer = Quad::from(h_rect);
+ let r = outer.size().min_comp() * 0.125;
+ let outer = outer.shrink(r);
+ let inner = outer.shrink(3.0 * r);
+ let col = ColorsLinear::adjust_for_state(self.cols.frame, state);
+ self.draw.rounded_frame(outer, inner, 0.0, col);
}
fn slider(&mut self, rect: Rect, h_rect: Rect, dir: Direction, state: InputState) {
// track
let mut outer = Quad::from(rect);
- outer = match dir.is_horizontal() {
- true => outer.shrink_vec(Vec2(0.0, outer.size().1 * (3.0 / 8.0))),
- false => outer.shrink_vec(Vec2(outer.size().0 * (3.0 / 8.0), 0.0)),
+ let mid = Vec2::from(h_rect.pos + h_rect.size / 2);
+ let (mut first, mut second);
+ if dir.is_horizontal() {
+ outer = outer.shrink_vec(Vec2(0.0, outer.size().1 * (1.0 / 3.0)));
+ first = outer;
+ second = outer;
+ first.b.0 = mid.0;
+ second.a.0 = mid.0;
+ } else {
+ outer = outer.shrink_vec(Vec2(outer.size().0 * (1.0 / 3.0), 0.0));
+ first = outer;
+ second = outer;
+ first.b.1 = mid.1;
+ second.a.1 = mid.1;
};
- let inner = outer.shrink(outer.size().min_comp() / 2.0);
- let col = self.cols.frame;
- self.draw.rounded_frame(outer, inner, 0.0, col);
- // handle
- self.draw_handle(h_rect, state);
+ let dist = outer.size().min_comp() / 2.0;
+ let inner = first.shrink(dist);
+ self.draw.rounded_frame(first, inner, 0.0, self.cols.accent);
+ let inner = second.shrink(dist);
+ self.draw
+ .rounded_frame(second, inner, 1.0 / 3.0, self.cols.frame);
+
+ // handle; force it to be square
+ let size = Size::splat(h_rect.size.0.min(h_rect.size.1));
+ let offset = Offset::from((h_rect.size - size) / 2);
+ let outer = Quad::from(Rect::new(h_rect.pos + offset, size));
+
+ let col = if state.nav_focus && !state.disabled {
+ self.cols.accent_soft
+ } else {
+ self.cols.background
+ };
+ let col = ColorsLinear::adjust_for_state(col, state);
+ self.draw.circle(outer, 0.0, col);
+ let col = self.cols.nav_region(state).unwrap_or(self.cols.frame);
+ self.draw.circle(outer, 14.0 / 16.0, col);
}
fn progress_bar(&mut self, rect: Rect, dir: Direction, _: InputState, value: f32) {
- let outer = Quad::from(rect);
- let mut inner = outer.shrink(self.window.dims.frame as f32);
- let col = self.cols.frame;
- self.draw.rounded_frame(outer, inner, BG_SHRINK_FACTOR, col);
+ let mut outer = Quad::from(rect);
+ let inner = outer.shrink(outer.size().min_comp() / 2.0);
+ self.draw.rounded_frame(outer, inner, 0.75, self.cols.frame);
if dir.is_horizontal() {
- inner.b.0 = inner.a.0 + value * (inner.b.0 - inner.a.0);
+ outer.b.0 = outer.a.0 + value * (outer.b.0 - outer.a.0);
} else {
- inner.b.1 = inner.a.1 + value * (inner.b.1 - inner.a.1);
+ outer.b.1 = outer.a.1 + value * (outer.b.1 - outer.a.1);
}
- let col = self.cols.button;
- self.draw.rect(inner, col);
+ let inner = outer.shrink(outer.size().min_comp() / 2.0);
+ self.draw.rounded_frame(outer, inner, 0.0, self.cols.accent);
}
fn image(&mut self, id: ImageId, rect: Rect) {
diff --git a/crates/kas-theme/src/shaded_theme.rs b/crates/kas-theme/src/shaded_theme.rs
index fab3807e1..0c0ea367e 100644
--- a/crates/kas-theme/src/shaded_theme.rs
+++ b/crates/kas-theme/src/shaded_theme.rs
@@ -58,9 +58,11 @@ impl ShadedTheme {
const DIMS: dim::Parameters = dim::Parameters {
outer_margin: 6.0,
inner_margin: 1.2,
+ frame_margin: 1.2,
text_margin: 2.0,
frame_size: 5.0,
button_frame: 5.0,
+ checkbox_inner: 9.0,
scrollbar_size: Vec2::splat(8.0),
slider_size: Vec2(12.0, 25.0),
progress_bar: Vec2::splat(12.0),
@@ -205,7 +207,7 @@ where
let outer = Quad::from(rect);
let thickness = outer.size().min_comp() / 2.0;
let inner = outer.shrink(thickness);
- let col = self.cols.scrollbar_state(state);
+ let col = self.cols.accent_soft_state(state);
self.draw.shaded_round_frame(outer, inner, (0.0, 0.6), col);
if let Some(col) = self.cols.nav_region(state) {
@@ -317,7 +319,7 @@ where
fn button(&mut self, rect: Rect, col: Option, state: InputState) {
let outer = Quad::from(rect);
let inner = outer.shrink(self.window.dims.button_frame as f32);
- let col = col.map(|c| c.into()).unwrap_or(self.cols.button);
+ let col = col.map(|c| c.into()).unwrap_or(self.cols.accent_soft);
let col = ColorsLinear::adjust_for_state(col, state);
self.draw.shaded_round_frame(outer, inner, (0.0, 0.6), col);
@@ -398,7 +400,7 @@ where
}
let thickness = outer.size().min_comp() / 2.0;
let inner = outer.shrink(thickness);
- let col = self.cols.button;
+ let col = self.cols.accent_soft;
self.draw.shaded_round_frame(outer, inner, (0.0, 0.6), col);
}
diff --git a/crates/kas-wgpu/src/draw/custom.rs b/crates/kas-wgpu/src/draw/custom.rs
index e607e8c04..8649594de 100644
--- a/crates/kas-wgpu/src/draw/custom.rs
+++ b/crates/kas-wgpu/src/draw/custom.rs
@@ -61,7 +61,9 @@ pub trait CustomPipe: 'static {
type Window: CustomWindow;
/// Construct a window associated with this pipeline
- fn new_window(&self, device: &wgpu::Device, size: Size) -> Self::Window;
+ ///
+ /// Note: [`Self::resize`] will be called before usage.
+ fn new_window(&self, device: &wgpu::Device) -> Self::Window;
/// Called whenever the window is resized
fn resize(
@@ -164,7 +166,7 @@ pub enum Void {}
/// A dummy implementation (does nothing)
impl CustomPipe for () {
type Window = ();
- fn new_window(&self, _: &wgpu::Device, _: Size) -> Self::Window {}
+ fn new_window(&self, _: &wgpu::Device) -> Self::Window {}
fn resize(&self, _: &mut Self::Window, _: &wgpu::Device, _: &wgpu::Queue, _: Size) {}
}
diff --git a/crates/kas-wgpu/src/draw/draw_pipe.rs b/crates/kas-wgpu/src/draw/draw_pipe.rs
index b251e32a6..50127b0ac 100644
--- a/crates/kas-wgpu/src/draw/draw_pipe.rs
+++ b/crates/kas-wgpu/src/draw/draw_pipe.rs
@@ -12,7 +12,7 @@ use super::*;
use kas::cast::Cast;
use kas::draw::color::Rgba;
use kas::draw::*;
-use kas::geom::{Coord, Quad, Rect, Size, Vec2};
+use kas::geom::{Quad, Rect, Size, Vec2};
use kas::text::{Effect, TextDisplay};
use kas_theme::DrawShadedImpl;
@@ -96,17 +96,12 @@ impl DrawPipe {
}
/// Construct per-window state
- pub fn new_window(&self, size: Size) -> DrawWindow {
- let vsize = Vec2::from(size);
- let scale: Scale = [-0.5 * vsize.0, 0.5 * vsize.1, 2.0 / vsize.0, -2.0 / vsize.1];
-
- let rect = Rect::new(Coord::ZERO, size);
-
- let custom = self.custom.new_window(&self.device, size);
+ pub fn new_window(&self) -> DrawWindow {
+ let custom = self.custom.new_window(&self.device);
DrawWindow {
- scale,
- clip_regions: vec![(rect, Offset::ZERO)],
+ scale: Default::default(),
+ clip_regions: vec![Default::default()],
images: Default::default(),
shaded_square: Default::default(),
shaded_round: Default::default(),
@@ -130,12 +125,9 @@ impl DrawPipe {
window.clip_regions[0].0.size = size;
let vsize = Vec2::from(size);
- window.scale = [
- -0.5 * vsize.0,
- -0.5 * vsize.1,
- 2.0 / vsize.0,
- -2.0 / vsize.1,
- ];
+ let off = vsize * -0.5;
+ let scale = 2.0 / vsize;
+ window.scale = [off.0, off.1, scale.0, -scale.1];
self.custom
.resize(&mut window.custom, &self.device, &self.queue, size);
diff --git a/crates/kas-wgpu/src/draw/flat_round.rs b/crates/kas-wgpu/src/draw/flat_round.rs
index 5f62ddc4b..5154f8414 100644
--- a/crates/kas-wgpu/src/draw/flat_round.rs
+++ b/crates/kas-wgpu/src/draw/flat_round.rs
@@ -12,8 +12,8 @@ use kas::geom::{Quad, Vec2};
use std::mem::size_of;
/// Offset relative to the size of a pixel used by the fragment shader to
-/// implement multi-sampling.
-const OFFSET: f32 = 0.125;
+/// implement 4x multi-sampling. The pattern is defined by the fragment shader.
+const AA_OFFSET: f32 = 0.5 * std::f32::consts::FRAC_1_SQRT_2;
// NOTE(opt): in theory we could reduce data transmission to the GPU by 1/3 by
// sending quads (two triangles) as instances in triangle-strip mode. The
@@ -56,7 +56,7 @@ impl Pipeline {
label: Some("FR render_pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
- module: &shaders.vert_122,
+ module: &shaders.vert_flat_round,
entry_point: "main",
buffers: &[wgpu::VertexBufferLayout {
array_stride: size_of::() as wgpu::BufferAddress,
@@ -130,7 +130,7 @@ impl Window {
let na = -nb;
// Since we take the mid-point, all offsets are uniform
- let p = Vec2::splat(OFFSET / radius);
+ let p = Vec2::splat(AA_OFFSET / radius);
let p1my = p1 - vy;
let p1py = p1 + vy;
@@ -163,7 +163,7 @@ impl Window {
]);
}
- /// Bounds on input: `0 ≤ inner_radius ≤ 1`.
+ /// Bounds on input: `0 ≤ inner_radius ≤ 1`.
pub fn circle(&mut self, pass: PassId, rect: Quad, inner_radius: f32, col: Rgba) {
let aa = rect.a;
let bb = rect.b;
@@ -173,20 +173,21 @@ impl Window {
return;
}
- let inner = inner_radius.max(0.0).min(1.0);
+ let inner = inner_radius.clamp(0.0, 1.0);
+ let inner = inner * inner; // shader compares to square
let ab = Vec2(aa.0, bb.1);
let ba = Vec2(bb.0, aa.1);
let mid = (aa + bb) * 0.5;
let n0 = Vec2::splat(0.0);
- let nb = (bb - aa).sign();
+ let nb = Vec2::ONE; // = (bb - aa).sign();
let na = -nb;
let nab = Vec2(na.0, nb.1);
let nba = Vec2(nb.0, na.1);
// Since we take the mid-point, all offsets are uniform
- let p = nb / (bb - mid) * OFFSET;
+ let p = nb / (bb - mid) * AA_OFFSET;
let aa = Vertex::new2(aa, col, inner, na, p);
let ab = Vertex::new2(ab, col, inner, nab, p);
@@ -249,10 +250,10 @@ impl Window {
let n0a = Vec2(0.0, na.1);
let n0b = Vec2(0.0, nb.1);
- let paa = na / (aa - cc) * OFFSET;
- let pab = nab / (ab - cd) * OFFSET;
- let pba = nba / (ba - dc) * OFFSET;
- let pbb = nb / (bb - dd) * OFFSET;
+ let paa = na / (aa - cc) * AA_OFFSET;
+ let pab = nab / (ab - cd) * AA_OFFSET;
+ let pba = nba / (ba - dc) * AA_OFFSET;
+ let pbb = nb / (bb - dd) * AA_OFFSET;
// We must add corners separately to ensure correct interpolation of dir
// values, hence need 16 points:
diff --git a/crates/kas-wgpu/src/draw/shaded_round.rs b/crates/kas-wgpu/src/draw/shaded_round.rs
index 0a258612a..efeafb0fa 100644
--- a/crates/kas-wgpu/src/draw/shaded_round.rs
+++ b/crates/kas-wgpu/src/draw/shaded_round.rs
@@ -13,8 +13,8 @@ use std::f32::consts::FRAC_PI_2;
use std::mem::size_of;
/// Offset relative to the size of a pixel used by the fragment shader to
-/// implement multi-sampling.
-const OFFSET: f32 = 0.125;
+/// implement 4x multi-sampling. The pattern is defined by the fragment shader.
+const OFFSET: f32 = 0.5 * std::f32::consts::FRAC_1_SQRT_2;
#[repr(C)]
#[derive(Clone, Copy, Debug)]
@@ -52,7 +52,7 @@ impl Pipeline {
label: Some("SR render_pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
- module: &shaders.vert_222,
+ module: &shaders.vert_shaded_round,
entry_point: "main",
buffers: &[wgpu::VertexBufferLayout {
array_stride: size_of::() as wgpu::BufferAddress,
diff --git a/crates/kas-wgpu/src/draw/shaded_square.rs b/crates/kas-wgpu/src/draw/shaded_square.rs
index 8214c09f7..d6513db1a 100644
--- a/crates/kas-wgpu/src/draw/shaded_square.rs
+++ b/crates/kas-wgpu/src/draw/shaded_square.rs
@@ -41,7 +41,7 @@ impl Pipeline {
label: Some("SS render_pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
- module: &shaders.vert_2,
+ module: &shaders.vert_shaded_square,
entry_point: "main",
buffers: &[wgpu::VertexBufferLayout {
array_stride: size_of::() as wgpu::BufferAddress,
diff --git a/crates/kas-wgpu/src/draw/shaders.rs b/crates/kas-wgpu/src/draw/shaders.rs
index e2339c715..5257aa8a5 100644
--- a/crates/kas-wgpu/src/draw/shaders.rs
+++ b/crates/kas-wgpu/src/draw/shaders.rs
@@ -9,9 +9,9 @@ use wgpu::{include_spirv, ShaderModule};
/// Shader manager
pub struct ShaderManager {
- pub vert_122: ShaderModule,
- pub vert_2: ShaderModule,
- pub vert_222: ShaderModule,
+ pub vert_flat_round: ShaderModule,
+ pub vert_shaded_square: ShaderModule,
+ pub vert_shaded_round: ShaderModule,
pub vert_image: ShaderModule,
pub vert_glyph: ShaderModule,
pub frag_flat_round: ShaderModule,
@@ -29,9 +29,9 @@ macro_rules! create {
impl ShaderManager {
pub fn new(device: &wgpu::Device) -> Self {
- let vert_122 = create!(device, "shaders/scaled_122.vert.spv");
- let vert_2 = create!(device, "shaders/scaled_2.vert.spv");
- let vert_222 = create!(device, "shaders/scaled_222.vert.spv");
+ let vert_flat_round = create!(device, "shaders/flat_round.vert.spv");
+ let vert_shaded_square = create!(device, "shaders/shaded_square.vert.spv");
+ let vert_shaded_round = create!(device, "shaders/shaded_round.vert.spv");
let vert_image = create!(device, "shaders/image.vert.spv");
let vert_glyph = create!(device, "shaders/glyph.vert.spv");
@@ -44,9 +44,9 @@ impl ShaderManager {
ShaderManager {
vert_image,
vert_glyph,
- vert_122,
- vert_2,
- vert_222,
+ vert_flat_round,
+ vert_shaded_square,
+ vert_shaded_round,
frag_flat_round,
frag_shaded_square,
frag_shaded_round,
diff --git a/crates/kas-wgpu/src/draw/shaders/flat_round.frag b/crates/kas-wgpu/src/draw/shaders/flat_round.frag
index e540dbea2..efae18976 100644
--- a/crates/kas-wgpu/src/draw/shaders/flat_round.frag
+++ b/crates/kas-wgpu/src/draw/shaders/flat_round.frag
@@ -23,8 +23,8 @@ float sample_a(vec2 pos) {
void main() {
// Multi-sample alpha to avoid ugly aliasing.
- vec2 off1 = vec2(off.x, 3.0 * off.y);
- vec2 off2 = vec2(3.0 * off.x, off.y);
+ vec2 off1 = vec2(off.x, 0.0);
+ vec2 off2 = vec2(0.0, off.y);
float alpha = sample_a(pos + off1)
+ sample_a(pos - off1)
+ sample_a(pos + off2)
diff --git a/crates/kas-wgpu/src/draw/shaders/flat_round.frag.spv b/crates/kas-wgpu/src/draw/shaders/flat_round.frag.spv
index be16d4cbc..5e9b85c74 100644
Binary files a/crates/kas-wgpu/src/draw/shaders/flat_round.frag.spv and b/crates/kas-wgpu/src/draw/shaders/flat_round.frag.spv differ
diff --git a/crates/kas-wgpu/src/draw/shaders/scaled_122.vert b/crates/kas-wgpu/src/draw/shaders/flat_round.vert
similarity index 100%
rename from crates/kas-wgpu/src/draw/shaders/scaled_122.vert
rename to crates/kas-wgpu/src/draw/shaders/flat_round.vert
diff --git a/crates/kas-wgpu/src/draw/shaders/scaled_122.vert.spv b/crates/kas-wgpu/src/draw/shaders/flat_round.vert.spv
similarity index 100%
rename from crates/kas-wgpu/src/draw/shaders/scaled_122.vert.spv
rename to crates/kas-wgpu/src/draw/shaders/flat_round.vert.spv
diff --git a/crates/kas-wgpu/src/draw/shaders/shaded_round.frag b/crates/kas-wgpu/src/draw/shaders/shaded_round.frag
index 2cf41e3ce..41e4674b4 100644
--- a/crates/kas-wgpu/src/draw/shaders/shaded_round.frag
+++ b/crates/kas-wgpu/src/draw/shaders/shaded_round.frag
@@ -27,8 +27,8 @@ float sample_a(vec2 dir) {
void main() {
// Multi-sample alpha to avoid ugly aliasing. A single colour sample is adequate.
- vec2 off1 = vec2(off.x, 3.0 * off.y);
- vec2 off2 = vec2(3.0 * off.x, off.y);
+ vec2 off1 = vec2(off.x, 0.0);
+ vec2 off2 = vec2(0.0, off.y);
float alpha = sample_a(dir + off1)
+ sample_a(dir - off1)
+ sample_a(dir + off2)
diff --git a/crates/kas-wgpu/src/draw/shaders/shaded_round.frag.spv b/crates/kas-wgpu/src/draw/shaders/shaded_round.frag.spv
index 7fbe5cc1c..4c3418e3c 100644
Binary files a/crates/kas-wgpu/src/draw/shaders/shaded_round.frag.spv and b/crates/kas-wgpu/src/draw/shaders/shaded_round.frag.spv differ
diff --git a/crates/kas-wgpu/src/draw/shaders/scaled_222.vert b/crates/kas-wgpu/src/draw/shaders/shaded_round.vert
similarity index 100%
rename from crates/kas-wgpu/src/draw/shaders/scaled_222.vert
rename to crates/kas-wgpu/src/draw/shaders/shaded_round.vert
diff --git a/crates/kas-wgpu/src/draw/shaders/scaled_222.vert.spv b/crates/kas-wgpu/src/draw/shaders/shaded_round.vert.spv
similarity index 100%
rename from crates/kas-wgpu/src/draw/shaders/scaled_222.vert.spv
rename to crates/kas-wgpu/src/draw/shaders/shaded_round.vert.spv
diff --git a/crates/kas-wgpu/src/draw/shaders/scaled_2.vert b/crates/kas-wgpu/src/draw/shaders/shaded_square.vert
similarity index 100%
rename from crates/kas-wgpu/src/draw/shaders/scaled_2.vert
rename to crates/kas-wgpu/src/draw/shaders/shaded_square.vert
diff --git a/crates/kas-wgpu/src/draw/shaders/scaled_2.vert.spv b/crates/kas-wgpu/src/draw/shaders/shaded_square.vert.spv
similarity index 100%
rename from crates/kas-wgpu/src/draw/shaders/scaled_2.vert.spv
rename to crates/kas-wgpu/src/draw/shaders/shaded_square.vert.spv
diff --git a/crates/kas-wgpu/src/window.rs b/crates/kas-wgpu/src/window.rs
index f89a196ce..23dfcfe55 100644
--- a/crates/kas-wgpu/src/window.rs
+++ b/crates/kas-wgpu/src/window.rs
@@ -53,9 +53,7 @@ impl>> Window {
) -> Result {
let time = Instant::now();
- // Create draw immediately (with Size::ZERO) to find ideal window size
let scale_factor = shared.scale_factor as f32;
- let mut draw = shared.draw.draw.new_window(Size::ZERO);
let mut theme_window = shared.theme.new_window(scale_factor);
let mut mgr = ManagerState::new(shared.config.clone());
@@ -87,7 +85,7 @@ impl>> Window {
let size: Size = window.inner_size().into();
info!("Constucted new window with size {:?}", size);
- // draw was initially created with Size::ZERO; we must resize
+ let mut draw = shared.draw.draw.new_window();
shared.draw.draw.resize(&mut draw, size);
let surface = unsafe { shared.instance.create_surface(&window) };
diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs
index ad7725cdc..6aa293ec8 100644
--- a/crates/kas-widgets/src/button.rs
+++ b/crates/kas-widgets/src/button.rs
@@ -23,6 +23,7 @@ pub struct Button, M: 'static> {
keys1: VirtualKeyCodes,
frame_size: Size,
frame_offset: Offset,
+ ideal_size: Size,
color: Option,
#[widget_derive]
#[widget]
@@ -37,6 +38,7 @@ impl, M: 'static> Debug for Button {
.field("keys1", &self.keys1)
.field("frame_size", &self.frame_size)
.field("frame_offset", &self.frame_offset)
+ .field("ideal_size", &self.ideal_size)
.field("color", &self.color)
.field("label", &self.label)
.finish_non_exhaustive()
@@ -64,10 +66,14 @@ impl, M: 'static> Layout for Button {
let (rules, offset, size) = frame_rules.surround_as_margin(content_rules);
self.frame_size.set_component(axis, size);
self.frame_offset.set_component(axis, offset);
+ self.ideal_size.set_component(axis, rules.ideal_size());
rules
}
- fn set_rect(&mut self, mgr: &mut Manager, mut rect: Rect, align: AlignHints) {
+ fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) {
+ let mut rect = align
+ .complete(Align::Centre, Align::Centre)
+ .aligned_rect(self.ideal_size, rect);
self.core.rect = rect;
rect.pos += self.frame_offset;
rect.size -= self.frame_size;
@@ -89,6 +95,7 @@ impl> Button {
keys1: Default::default(),
frame_size: Default::default(),
frame_offset: Default::default(),
+ ideal_size: Default::default(),
color: None,
label,
on_push: None,
@@ -110,6 +117,7 @@ impl> Button {
keys1: self.keys1,
frame_size: self.frame_size,
frame_offset: self.frame_offset,
+ ideal_size: self.ideal_size,
color: self.color,
label: self.label,
on_push: Some(Rc::new(f)),
@@ -205,6 +213,7 @@ pub struct TextButton {
keys1: VirtualKeyCodes,
frame_size: Size,
frame_offset: Offset,
+ ideal_size: Size,
color: Option,
label: Text,
on_push: Option Option>>,
@@ -217,6 +226,7 @@ impl Debug for TextButton {
.field("keys1", &self.keys1)
.field("frame_size", &self.frame_size)
.field("frame_offset", &self.frame_offset)
+ .field("ideal_size", &self.ideal_size)
.field("color", &self.color)
.field("label", &self.label)
.finish_non_exhaustive()
@@ -245,10 +255,14 @@ impl Layout for TextButton {
let (rules, offset, size) = frame_rules.surround_as_margin(content_rules);
self.frame_size.set_component(axis, size);
self.frame_offset.set_component(axis, offset);
+ self.ideal_size.set_component(axis, rules.ideal_size());
rules
}
fn set_rect(&mut self, _: &mut Manager, rect: Rect, align: AlignHints) {
+ let rect = align
+ .complete(Align::Centre, Align::Centre)
+ .aligned_rect(self.ideal_size, rect);
self.core.rect = rect;
let size = rect.size - self.frame_size;
self.label.update_env(|env| {
@@ -276,6 +290,7 @@ impl TextButton {
keys1: Default::default(),
frame_size: Default::default(),
frame_offset: Default::default(),
+ ideal_size: Default::default(),
color: None,
label: text,
on_push: None,
@@ -297,6 +312,7 @@ impl TextButton {
keys1: self.keys1,
frame_size: self.frame_size,
frame_offset: self.frame_offset,
+ ideal_size: self.ideal_size,
color: self.color,
label: self.label,
on_push: Some(Rc::new(f)),
diff --git a/crates/kas-widgets/src/editbox.rs b/crates/kas-widgets/src/editbox.rs
index 948179c2e..c7e2494a9 100644
--- a/crates/kas-widgets/src/editbox.rs
+++ b/crates/kas-widgets/src/editbox.rs
@@ -353,7 +353,7 @@ impl Layout for EditBox {
if !self.rect().contains(coord) {
return None;
}
- self.inner.find_id(coord).or(Some(self.id()))
+ Some(self.inner.id())
}
fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) {
@@ -407,7 +407,7 @@ impl std::ops::DerefMut for EditBox {
/// line-wrapping and a larger vertical height). This mode is only recommended
/// for short texts for performance reasons.
#[derive(Clone, Default, Debug, Widget)]
-#[widget(config(key_nav = true, cursor_icon = event::CursorIcon::Text))]
+#[widget(config(key_nav = true, hover_highlight = true, cursor_icon = event::CursorIcon::Text))]
#[handler(handle=noauto, generics = <> where G: EditGuard)]
pub struct EditField {
#[widget_core]
diff --git a/crates/kas-widgets/src/progress.rs b/crates/kas-widgets/src/progress.rs
index 2a6b87c8e..03ab01636 100644
--- a/crates/kas-widgets/src/progress.rs
+++ b/crates/kas-widgets/src/progress.rs
@@ -17,6 +17,7 @@ pub struct ProgressBar {
#[widget_core]
core: CoreData,
direction: D,
+ width: i32,
value: f32,
}
@@ -39,6 +40,7 @@ impl ProgressBar {
ProgressBar {
core: Default::default(),
direction,
+ width: 0,
value: 0.0,
}
}
@@ -81,10 +83,20 @@ impl Layout for ProgressBar {
if self.direction.is_vertical() == axis.is_vertical() {
SizeRules::new(size.0, size.0, margins, Stretch::High)
} else {
+ self.width = size.1;
SizeRules::fixed(size.1, margins)
}
}
+ fn set_rect(&mut self, _: &mut Manager, rect: Rect, align: AlignHints) {
+ let mut ideal_size = Size::splat(self.width);
+ ideal_size.set_component(self.direction, i32::MAX);
+ let rect = align
+ .complete(Align::Centre, Align::Centre)
+ .aligned_rect(ideal_size, rect);
+ self.core.rect = rect;
+ }
+
fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool) {
let dir = self.direction.as_direction();
let state = self.input_state(mgr, disabled);
diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs
index 1c52fd58a..70460c4f2 100644
--- a/crates/kas-widgets/src/scrollbar.rs
+++ b/crates/kas-widgets/src/scrollbar.rs
@@ -21,6 +21,7 @@ pub struct ScrollBar {
core: CoreData,
direction: D,
// Terminology assumes vertical orientation:
+ width: i32,
min_handle_len: i32,
handle_len: i32,
handle_value: i32, // contract: > 0
@@ -48,6 +49,7 @@ impl ScrollBar {
ScrollBar {
core: Default::default(),
direction,
+ width: 0,
min_handle_len: 0,
handle_len: 0,
handle_value: 1,
@@ -207,11 +209,17 @@ impl Layout for ScrollBar {
if self.direction.is_vertical() == axis.is_vertical() {
SizeRules::new(min_len, min_len, margins, Stretch::High)
} else {
+ self.width = size.1;
SizeRules::fixed(size.1, margins)
}
}
fn set_rect(&mut self, mgr: &mut Manager, rect: Rect, align: AlignHints) {
+ let mut ideal_size = Size::splat(self.width);
+ ideal_size.set_component(self.direction, i32::MAX);
+ let rect = align
+ .complete(Align::Centre, Align::Centre)
+ .aligned_rect(ideal_size, rect);
self.core.rect = rect;
self.handle.set_rect(mgr, rect, align);
let _ = self.update_handle();
@@ -491,6 +499,17 @@ impl ScrollBars {
pub fn inner_mut(&mut self) -> &mut W {
&mut self.inner
}
+
+ fn draw_(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) {
+ let disabled = disabled || self.is_disabled();
+ if self.show_bars.0 {
+ self.horiz_bar.draw(draw_handle, mgr, disabled);
+ }
+ if self.show_bars.1 {
+ self.vert_bar.draw(draw_handle, mgr, disabled);
+ }
+ self.inner.draw(draw_handle, mgr, disabled);
+ }
}
impl Scrollable for ScrollBars {
@@ -578,15 +597,38 @@ impl Layout for ScrollBars {
.or(Some(self.id()))
}
+ #[cfg(feature = "min_spec")]
+ default fn draw(
+ &self,
+ draw_handle: &mut dyn DrawHandle,
+ mgr: &event::ManagerState,
+ disabled: bool,
+ ) {
+ self.draw_(draw_handle, mgr, disabled);
+ }
+ #[cfg(not(feature = "min_spec"))]
fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) {
- let disabled = disabled || self.is_disabled();
- if self.show_bars.0 {
- self.horiz_bar.draw(draw_handle, mgr, disabled);
- }
- if self.show_bars.1 {
- self.vert_bar.draw(draw_handle, mgr, disabled);
- }
- self.inner.draw(draw_handle, mgr, disabled);
+ self.draw_(draw_handle, mgr, disabled);
+ }
+}
+
+#[cfg(feature = "min_spec")]
+impl Layout for ScrollBars> {
+ fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) {
+ let disabled = disabled || self.is_disabled() || self.inner.is_disabled();
+ // Enlarge clip region to *our* rect:
+ draw_handle.with_clip_region(self.core.rect, self.inner.scroll_offset(), &mut |handle| {
+ self.inner.inner().draw(handle, mgr, disabled)
+ });
+ // Use a second clip region to force draw order:
+ draw_handle.with_clip_region(self.core.rect, Offset::ZERO, &mut |draw_handle| {
+ if self.show_bars.0 {
+ self.horiz_bar.draw(draw_handle, mgr, disabled);
+ }
+ if self.show_bars.1 {
+ self.vert_bar.draw(draw_handle, mgr, disabled);
+ }
+ });
}
}
diff --git a/crates/kas-widgets/src/separator.rs b/crates/kas-widgets/src/separator.rs
index fcbc0a0a7..855a85890 100644
--- a/crates/kas-widgets/src/separator.rs
+++ b/crates/kas-widgets/src/separator.rs
@@ -49,7 +49,8 @@ impl Separator {
impl Layout for Separator {
fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules {
- SizeRules::extract_fixed(axis, size_handle.separator(), Default::default())
+ let margins = size_handle.frame_margins();
+ SizeRules::extract_fixed(axis, size_handle.separator(), margins)
}
fn draw(&self, draw_handle: &mut dyn DrawHandle, _: &event::ManagerState, _: bool) {
diff --git a/examples/gallery.rs b/examples/gallery.rs
index 08b343334..af9bcebb4 100644
--- a/examples/gallery.rs
+++ b/examples/gallery.rs
@@ -103,8 +103,8 @@ fn main() -> Result<(), kas::shell::Error> {
#[cfg(feature = "stack_dst")]
let theme = kas::theme::MultiTheme::builder()
- .add("shaded", kas::theme::ShadedTheme::new())
.add("flat", kas::theme::FlatTheme::new())
+ .add("shaded", kas::theme::ShadedTheme::new())
.build();
#[cfg(not(feature = "stack_dst"))]
let theme = kas::theme::ShadedTheme::new();
@@ -119,8 +119,8 @@ fn main() -> Result<(), kas::shell::Error> {
}
let themes = vec![
- MenuEntry::new("&Shaded", Menu::Theme("shaded")).boxed_menu(),
MenuEntry::new("&Flat", Menu::Theme("flat")).boxed_menu(),
+ MenuEntry::new("&Shaded", Menu::Theme("shaded")).boxed_menu(),
];
// Enumerate colour schemes. Access through the toolkit since this handles
// config loading.
@@ -253,7 +253,7 @@ fn main() -> Result<(), kas::shell::Error> {
.on_select(|_, index| Some(Item::Combo((index + 1).cast()))),
#[widget(row=8, col=0)] _ = Label::new("Slider"),
#[widget(row=8, col=1, handler = handle_slider)] s =
- Slider::::new(-2, 2, 1).with_value(0),
+ Slider::::new(0, 10, 1).with_value(0),
#[widget(row=9, col=0)] _ = Label::new("ScrollBar"),
#[widget(row=9, col=1, handler = handle_scroll)] sc: ScrollBar =
ScrollBar::new().with_limits(100, 20),
@@ -324,7 +324,7 @@ fn main() -> Result<(), kas::shell::Error> {
fn activations(&mut self, mgr: &mut Manager, item: Item) -> VoidResponse {
match item {
Item::Button => println!("Clicked!"),
- Item::LightTheme => mgr.adjust_theme(|theme| theme.set_scheme("")),
+ Item::LightTheme => mgr.adjust_theme(|theme| theme.set_scheme("light")),
Item::DarkTheme => mgr.adjust_theme(|theme| theme.set_scheme("dark")),
Item::Check(b) => println!("CheckBox: {}", b),
Item::Combo(c) => println!("ComboBox: {}", c),
diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs
index 06dc36962..0cdbd07c2 100644
--- a/examples/mandlebrot/mandlebrot.rs
+++ b/examples/mandlebrot/mandlebrot.rs
@@ -147,7 +147,7 @@ struct PipeWindow {
impl CustomPipe for Pipe {
type Window = PipeWindow;
- fn new_window(&self, _: &wgpu::Device, _: Size) -> Self::Window {
+ fn new_window(&self, _: &wgpu::Device) -> Self::Window {
let push_constants = PushConstants {
p: DVec2::splat(0.0),
q: DVec2::splat(1.0),