Separate theming engine from widget painter
This commit is contained in:
parent
31b355c727
commit
dc62753629
3 changed files with 162 additions and 90 deletions
|
@ -3,3 +3,4 @@ pub use ui::draw;
|
||||||
pub use ui::redraw_quick;
|
pub use ui::redraw_quick;
|
||||||
|
|
||||||
mod painter;
|
mod painter;
|
||||||
|
mod theme;
|
||||||
|
|
|
@ -1,49 +1,6 @@
|
||||||
type Color = (f64, f64, f64, f64);
|
use log::debug;
|
||||||
|
|
||||||
struct Theme {
|
use super::theme::{Theme, ThemedContextExt};
|
||||||
background: Color,
|
|
||||||
|
|
||||||
frame: Color,
|
|
||||||
frame_width: f64,
|
|
||||||
|
|
||||||
input_bg: Color,
|
|
||||||
input_font_color: Color,
|
|
||||||
input_font_size: f64,
|
|
||||||
|
|
||||||
result_bg: Color,
|
|
||||||
result_bg_selected: Color,
|
|
||||||
result_font_color: Color,
|
|
||||||
result_font_selected: Color,
|
|
||||||
result_font_size: f64,
|
|
||||||
|
|
||||||
divider: Color,
|
|
||||||
divider_width: f64
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Theme {
|
|
||||||
fn default() -> Self {
|
|
||||||
let bg_color = (0.5, 0.5, 1.0, 1.0);
|
|
||||||
Theme {
|
|
||||||
background: bg_color,
|
|
||||||
|
|
||||||
frame: (1.0, 0.0, 0.0, 1.0),
|
|
||||||
frame_width: 5.0,
|
|
||||||
|
|
||||||
input_bg: (0.5, 0.5, 1.0, 1.0),
|
|
||||||
input_font_color: (0.0, 0.0, 1.0, 1.0),
|
|
||||||
input_font_size: 20.0,
|
|
||||||
|
|
||||||
result_bg: (0.5, 0.5, 1.0, 1.0),
|
|
||||||
result_bg_selected: (0.5, 0.5, 0.9, 1.0),
|
|
||||||
result_font_color: (0.0, 0.0, 1.0, 1.0),
|
|
||||||
result_font_selected: (0.5, 0.5, 0.5, 1.0),
|
|
||||||
result_font_size: 20.0,
|
|
||||||
|
|
||||||
divider: (0.0, 0.0, 0.0, 1.0),
|
|
||||||
divider_width: 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Painter<'a> {
|
pub struct Painter<'a> {
|
||||||
context: &'a cairo::Context,
|
context: &'a cairo::Context,
|
||||||
|
@ -59,25 +16,39 @@ impl<'a> Painter<'a> {
|
||||||
{
|
{
|
||||||
let ctx = self.context;
|
let ctx = self.context;
|
||||||
|
|
||||||
// reset clip so that we can draw the entire background
|
// reset clip so that we can draw the entire background of the surface
|
||||||
ctx.reset_clip();
|
ctx.reset_clip();
|
||||||
|
|
||||||
// fill the background
|
// paint base color to clip region (i.e. entire surface)
|
||||||
let (r,g,b,a) = self.theme.background;
|
ctx.theme_color_base(&self.theme);
|
||||||
ctx.set_source_rgba(r, g, b, a);
|
|
||||||
ctx.set_operator(cairo::Operator::Source);
|
ctx.set_operator(cairo::Operator::Source);
|
||||||
ctx.paint().unwrap();
|
ctx.paint().expect("Could not paint background");
|
||||||
|
|
||||||
// draw the border
|
// draw the border
|
||||||
let (x1,y1,x2,y2) = ctx.clip_extents().unwrap();
|
let (x1,y1,x2,y2) = ctx.clip_extents().unwrap();
|
||||||
let (r,g,b,a) = self.theme.frame;
|
debug!{"Border width: {}", self.theme.border(ctx)};
|
||||||
|
let border = self.theme.border(ctx)/2.0;
|
||||||
|
let (x1,y1,x2,y2) = (x1+border,y1+border,x2-x1-border,y2-y1-border);
|
||||||
|
debug!{"Border rect: {},{},{},{}", x1, y1, x2, y2};
|
||||||
ctx.rectangle(x1,y1,x2-x1,y2-y1);
|
ctx.rectangle(x1,y1,x2-x1,y2-y1);
|
||||||
ctx.set_source_rgba(r, g, b, a);
|
ctx.theme_border(&self.theme);
|
||||||
let frame_width = self.device_to_user(self.theme.frame_width);
|
|
||||||
ctx.set_line_width(frame_width);
|
|
||||||
ctx.stroke().unwrap();
|
ctx.stroke().unwrap();
|
||||||
|
|
||||||
ctx.rectangle(x1+(frame_width/2.0), y1+(frame_width/2.0), x2-x1-frame_width, y2-y1-frame_width);
|
// ctx.reset_clip();
|
||||||
|
// // finally, clip any future content so the border stays alive
|
||||||
|
let (x1,y1,x2,y2) = ctx.clip_extents().unwrap(); // the path of the clip
|
||||||
|
// // region, i.e. the surface
|
||||||
|
|
||||||
|
debug!{"Border extent: {},{},{},{}", x1, y1, x2, y2};
|
||||||
|
let border = self.theme.border(ctx); // width of the border, note
|
||||||
|
// that half of the border is outside
|
||||||
|
// the clip_extents
|
||||||
|
|
||||||
|
// let (x1,y1,x2,y2) = (x1+border, y1+border, x2-border, y2-border);
|
||||||
|
debug!{"Border rect: {},{},{},{}", x1, y1, x2, y2};
|
||||||
|
let (x1,y1,x2,y2) = (x1+border,y1+border,x2-x1-border,y2-y1-border-border);
|
||||||
|
debug!{"Border clip: {},{},{},{}", x1, y1, x2, y2};
|
||||||
|
ctx.rectangle(x1,y1,x2,y2);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,19 +59,13 @@ impl<'a> Painter<'a> {
|
||||||
let (x, y): (f64, f64) = (x.into(), y.into());
|
let (x, y): (f64, f64) = (x.into(), y.into());
|
||||||
|
|
||||||
// draw background box
|
// draw background box
|
||||||
|
ctx.theme_color_base(&self.theme);
|
||||||
ctx.rectangle(x, y, self.clip_width(), 1.0);
|
ctx.rectangle(x, y, self.clip_width(), 1.0);
|
||||||
let (r,g,b,a) = self.theme.input_bg;
|
|
||||||
ctx.set_source_rgba(r,g,b,a);
|
|
||||||
ctx.fill().unwrap();
|
ctx.fill().unwrap();
|
||||||
|
|
||||||
// draw text
|
// draw text
|
||||||
ctx.select_font_face ("serif", cairo::FontSlant::Normal, cairo::FontWeight::Normal);
|
|
||||||
let font_size = self.device_to_user(self.theme.input_font_size);
|
|
||||||
ctx.set_font_size(font_size);
|
|
||||||
let (r,g,b,a) = self.theme.input_font_color;
|
|
||||||
ctx.set_source_rgba(r,g,b,a);
|
|
||||||
ctx.move_to(x + 0.2, y + 0.8);
|
ctx.move_to(x + 0.2, y + 0.8);
|
||||||
ctx.show_text(input).unwrap();
|
ctx.theme_text(&input, false, &[], &self.theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn result_box<T>(&self, x: T, y: T, result: &'a str, indices: &[usize], selected: bool)
|
pub fn result_box<T>(&self, x: T, y: T, result: &'a str, indices: &[usize], selected: bool)
|
||||||
|
@ -111,28 +76,14 @@ impl<'a> Painter<'a> {
|
||||||
|
|
||||||
// draw background box
|
// draw background box
|
||||||
ctx.rectangle(x.into(), y.into(), self.clip_width(), 1.0);
|
ctx.rectangle(x.into(), y.into(), self.clip_width(), 1.0);
|
||||||
let (r,g,b,a) = if selected { self.theme.result_bg_selected }
|
if selected { ctx.theme_color_highlight(&self.theme) }
|
||||||
else { self.theme.result_bg };
|
else { ctx.theme_color_base(&self.theme) };
|
||||||
ctx.set_source_rgba(r,g,b,a);
|
ctx.set_operator(cairo::Operator::Source);
|
||||||
ctx.fill().unwrap();
|
ctx.fill().unwrap();
|
||||||
|
|
||||||
// draw text
|
// draw text
|
||||||
let font_size = self.device_to_user(self.theme.result_font_size);
|
|
||||||
ctx.set_font_size(font_size);
|
|
||||||
let (r,g,b,a) = if selected { self.theme.result_font_selected }
|
|
||||||
else { self.theme.result_font_color };
|
|
||||||
ctx.set_source_rgba(r,g,b,a);
|
|
||||||
ctx.move_to (x+0.2, y+0.8);
|
ctx.move_to (x+0.2, y+0.8);
|
||||||
|
ctx.theme_text(&result, selected, indices, &self.theme)
|
||||||
for i in 0..result.len() {
|
|
||||||
if indices.contains(&i) {
|
|
||||||
ctx.select_font_face ("serif", cairo::FontSlant::Normal, cairo::FontWeight::Bold);
|
|
||||||
ctx.show_text(&result[i..i+1]).unwrap();
|
|
||||||
} else {
|
|
||||||
ctx.select_font_face ("serif", cairo::FontSlant::Normal, cairo::FontWeight::Normal);
|
|
||||||
ctx.show_text(&result[i..i+1]).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn divider<T>(&self, x: T, y: T)
|
pub fn divider<T>(&self, x: T, y: T)
|
||||||
|
@ -141,18 +92,13 @@ impl<'a> Painter<'a> {
|
||||||
let ctx = self.context;
|
let ctx = self.context;
|
||||||
let (x, y): (f64, f64) = (x.into(), y.into());
|
let (x, y): (f64, f64) = (x.into(), y.into());
|
||||||
|
|
||||||
|
ctx.theme_divider(&self.theme);
|
||||||
ctx.move_to(x, y);
|
ctx.move_to(x, y);
|
||||||
|
|
||||||
let dash_width = self.device_to_user(self.theme.divider_width);
|
|
||||||
ctx.set_line_width(dash_width);
|
|
||||||
ctx.line_to(self.clip_width(), y);
|
ctx.line_to(self.clip_width(), y);
|
||||||
ctx.set_dash(&[self.device_to_user(3.0)], 0.0);
|
|
||||||
let (r,g,b,a) = self.theme.divider;
|
|
||||||
ctx.set_source_rgba(r,g,b,a);
|
|
||||||
ctx.stroke().unwrap();
|
ctx.stroke().unwrap();
|
||||||
|
|
||||||
ctx.rectangle(x, y+dash_width, self.clip_width(), self.clip_height()-dash_width);
|
// ctx.rectangle(x, y+dash_width, self.clip_width(), self.clip_height()-dash_width);
|
||||||
ctx.clip();
|
// ctx.clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_to_user(&self, point: f64) -> f64 {
|
fn device_to_user(&self, point: f64) -> f64 {
|
||||||
|
|
125
src/ui/theme.rs
Normal file
125
src/ui/theme.rs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
// (r,g,b,a)
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Color(f64, f64, f64, f64);
|
||||||
|
// (name, size)
|
||||||
|
pub struct Font(String, f64);
|
||||||
|
|
||||||
|
struct ColorScheme {
|
||||||
|
base: Color,
|
||||||
|
border: Color,
|
||||||
|
highlight: Color,
|
||||||
|
|
||||||
|
divider: Color,
|
||||||
|
|
||||||
|
text: Color,
|
||||||
|
text_highlight: Color
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Theme {
|
||||||
|
colors: ColorScheme,
|
||||||
|
font: Font,
|
||||||
|
|
||||||
|
border: f64,
|
||||||
|
divider: f64
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Theme {
|
||||||
|
pub fn border(&self, ctx: &cairo::Context) -> f64 {
|
||||||
|
ctx.device_to_user_point(self.border)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Theme {
|
||||||
|
fn default() -> Self {
|
||||||
|
let colors = ColorScheme {
|
||||||
|
base: Color(0.13, 0.05, 0.23, 0.9),
|
||||||
|
border: Color(0.23, 0.05, 0.11, 1.0),
|
||||||
|
highlight: Color(0.12, 0.04, 0.08, 0.9),
|
||||||
|
|
||||||
|
divider: Color(0.23, 0.05, 0.11, 1.0),
|
||||||
|
|
||||||
|
text: Color(0.87, 0.95, 0.77, 1.0),
|
||||||
|
text_highlight: Color(0.6, 0.8, 0.4, 1.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
Theme {
|
||||||
|
colors,
|
||||||
|
font: Font("sans".into(), 17.0),
|
||||||
|
|
||||||
|
border: 2.0,
|
||||||
|
divider: 3.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ThemedContextExt {
|
||||||
|
fn theme_color_base(&self, theme: &Theme);
|
||||||
|
fn theme_color_highlight(&self, theme: &Theme);
|
||||||
|
|
||||||
|
fn theme_border(&self, theme: &Theme);
|
||||||
|
fn theme_divider(&self, theme: &Theme);
|
||||||
|
|
||||||
|
fn theme_text(&self, text: &str, selected: bool, indices: &[usize], theme: &Theme);
|
||||||
|
|
||||||
|
|
||||||
|
fn device_to_user_point(&self, point: f64) -> f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemedContextExt for cairo::Context {
|
||||||
|
fn theme_color_base(&self, theme: &Theme) {
|
||||||
|
let Color(r,g,b,a) = theme.colors.base;
|
||||||
|
self.set_source_rgba(r,g,b,a);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theme_color_highlight(&self, theme: &Theme) {
|
||||||
|
let Color(r,g,b,a) = theme.colors.highlight;
|
||||||
|
self.set_source_rgba(r,g,b,a);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theme_border(&self, theme: &Theme) {
|
||||||
|
let Color(r,g,b,a) = theme.colors.border;
|
||||||
|
self.set_source_rgba(r,g,b,a);
|
||||||
|
let border_width = self.device_to_user_point(theme.border);
|
||||||
|
self.set_line_width(border_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theme_divider(&self, theme: &Theme) {
|
||||||
|
let divider_width = self.device_to_user_point(theme.divider);
|
||||||
|
self.set_line_width(divider_width);
|
||||||
|
|
||||||
|
self.set_dash(&[self.device_to_user_point(3.0)], 0.0);
|
||||||
|
|
||||||
|
let Color(r,g,b,a) = theme.colors.divider;
|
||||||
|
self.set_source_rgba(r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theme_text(&self, text: &str, selected: bool, indices: &[usize], theme: &Theme) {
|
||||||
|
let Color(r,g,b,a) = if selected { theme.colors.text_highlight }
|
||||||
|
else { theme.colors.text };
|
||||||
|
self.set_source_rgba(r,g,b,a);
|
||||||
|
|
||||||
|
let font_size = self.device_to_user_point(theme.font.1);
|
||||||
|
self.set_font_size(font_size);
|
||||||
|
|
||||||
|
for i in 0..text.len() {
|
||||||
|
if indices.contains(&i) {
|
||||||
|
self.select_font_face(&theme.font.0,
|
||||||
|
cairo::FontSlant::Italic,
|
||||||
|
cairo::FontWeight::Normal);
|
||||||
|
} else {
|
||||||
|
self.select_font_face(&theme.font.0,
|
||||||
|
cairo::FontSlant::Normal,
|
||||||
|
cairo::FontWeight::Normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.show_text(&text[i..i+1])
|
||||||
|
.expect("Could not draw themed text");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device_to_user_point(&self, point: f64) -> f64 {
|
||||||
|
self.device_to_user(point, point)
|
||||||
|
.map(|(x,y)| if x>=y {x} else {y})
|
||||||
|
.expect("Could not convert device to user space")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue