use winit::keyboard::Key;
use super::{FrameStyle, MarkStyle, SelectionStyle, SizeCx, Text, ThemeSize};
use crate::dir::Direction;
use crate::draw::color::{ParseError, Rgb, Rgba};
use crate::draw::{Draw, DrawIface, DrawRounded, DrawShared, DrawSharedImpl, ImageId, PassType};
use crate::event::EventState;
#[allow(unused)] use crate::event::{Command, ConfigCx};
use crate::geom::{Coord, Offset, Rect};
use crate::text::{Effect, TextDisplay, format::FormattableText};
use crate::theme::ColorsLinear;
use crate::{Id, Tile, autoimpl};
#[allow(unused)] use crate::{Layout, theme::TextClass};
use std::ops::Range;
use std::time::Instant;
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub enum Background {
#[default]
Default,
Error,
Rgb(Rgb),
}
impl From<Rgb> for Background {
#[inline]
fn from(color: Rgb) -> Self {
Background::Rgb(color)
}
}
#[derive(Copy, Clone, Debug, thiserror::Error)]
pub enum BackgroundParseError {
#[error("Unknown: no `#` prefix")]
Unknown,
#[error("invalid hex sequence")]
InvalidRgb(#[from] ParseError),
}
impl std::str::FromStr for Background {
type Err = BackgroundParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("#") {
Rgb::from_str(s).map(|c| c.into()).map_err(|e| e.into())
} else {
Err(BackgroundParseError::Unknown)
}
}
}
#[autoimpl(Debug ignore self.h)]
pub struct DrawCx<'a> {
h: &'a mut dyn ThemeDraw,
id: Id,
}
impl<'a> DrawCx<'a> {
#[inline(always)]
pub fn re<'b>(&'b mut self) -> DrawCx<'b>
where
'a: 'b,
{
DrawCx {
h: self.h,
id: self.id.clone(),
}
}
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
pub(crate) fn new(h: &'a mut dyn ThemeDraw, id: Id) -> Self {
DrawCx { h, id }
}
pub fn set_id(&mut self, id: Id) {
self.id = id;
}
pub fn ev_state(&mut self) -> &mut EventState {
self.h.components().2
}
pub fn size_cx(&mut self) -> SizeCx<'_> {
let (w, _, es) = self.h.components();
SizeCx::new(es, w)
}
pub fn colors(&self) -> &ColorsLinear {
self.h.colors()
}
pub fn draw_shared(&mut self) -> &mut dyn DrawShared {
self.h.components().1.shared()
}
pub fn draw(&mut self) -> &mut dyn Draw {
self.h.components().1
}
pub fn draw_rounded(&mut self) -> Option<&mut dyn DrawRounded> {
self.h.draw_rounded()
}
pub fn draw_iface<DS: DrawSharedImpl>(&mut self) -> Option<DrawIface<'_, DS>> {
DrawIface::downcast_from(self.draw())
}
pub fn with_pass<F: FnOnce(DrawCx)>(&mut self, f: F) {
let clip_rect = self.h.get_clip_rect();
let id = self.id.clone();
self.h.new_pass(
clip_rect,
Offset::ZERO,
PassType::Clip,
Box::new(|h| f(DrawCx { h, id })),
);
}
pub fn with_clip_region<F: FnOnce(DrawCx)>(&mut self, rect: Rect, offset: Offset, f: F) {
let id = self.id.clone();
self.h.new_pass(
rect,
offset,
PassType::Clip,
Box::new(|h| f(DrawCx { h, id })),
);
}
pub fn with_overlay<F: FnOnce(DrawCx)>(&mut self, rect: Rect, offset: Offset, f: F) {
let id = self.id.clone();
self.h.new_pass(
rect,
offset,
PassType::Overlay,
Box::new(|h| f(DrawCx { h, id })),
);
}
pub fn get_clip_rect(&mut self) -> Rect {
self.h.get_clip_rect()
}
pub fn access_key(&mut self, id: &Id, key: &Key) -> bool {
self.ev_state().add_access_key_binding(id, key)
}
pub fn frame(&mut self, rect: Rect, style: FrameStyle, bg: Background) {
self.h.frame(&self.id, rect, style, bg)
}
pub fn separator(&mut self, rect: Rect) {
self.h.separator(rect);
}
pub fn selection(&mut self, rect: Rect, style: SelectionStyle) {
self.h.selection(rect, style);
}
pub fn text<T: FormattableText>(&mut self, rect: Rect, text: &Text<T>) {
self.text_with_position(rect.pos, rect, text);
}
pub fn text_with_color<T: FormattableText>(&mut self, rect: Rect, text: &Text<T>, color: Rgba) {
let effects = text.effect_tokens();
self.text_with_effects(rect.pos, rect, text, &[color], effects);
}
pub fn text_with_position<T: FormattableText>(
&mut self,
pos: Coord,
rect: Rect,
text: &Text<T>,
) {
let effects = text.effect_tokens();
self.text_with_effects(pos, rect, text, &[], effects);
}
pub fn text_with_effects<T: FormattableText>(
&mut self,
pos: Coord,
rect: Rect,
text: &Text<T>,
colors: &[Rgba],
effects: &[Effect],
) {
if let Ok(display) = text.display() {
if effects.is_empty() {
self.h
.text(&self.id, pos, rect, display, colors.first().cloned());
} else {
if cfg!(debug_assertions) {
let num_colors = if colors.is_empty() { 1 } else { colors.len() };
let mut i = 0;
for effect in effects {
assert!(effect.start >= i);
i = effect.start;
assert!(usize::from(effect.e) < num_colors);
}
}
self.h
.text_effects(&self.id, pos, rect, display, colors, effects);
}
}
}
pub fn text_with_selection<T: FormattableText>(
&mut self,
pos: Coord,
rect: Rect,
text: &Text<T>,
range: Range<usize>,
) {
if range.is_empty() {
return self.text_with_position(pos, rect, text);
}
let Ok(display) = text.display() else {
return;
};
self.h
.text_selected_range(&self.id, pos, rect, display, range);
}
pub fn text_cursor<T: FormattableText>(
&mut self,
pos: Coord,
rect: Rect,
text: &Text<T>,
byte: usize,
) {
if let Ok(text) = text.display() {
self.h.text_cursor(&self.id, pos, rect, text, byte);
}
}
pub fn check_box(&mut self, rect: Rect, checked: bool, last_change: Option<Instant>) {
self.h.check_box(&self.id, rect, checked, last_change);
}
pub fn radio_box(&mut self, rect: Rect, checked: bool, last_change: Option<Instant>) {
self.h.radio_box(&self.id, rect, checked, last_change);
}
pub fn mark(&mut self, rect: Rect, style: MarkStyle) {
self.h.mark(&self.id, rect, style);
}
pub fn scroll_bar<W: Tile>(&mut self, track_rect: Rect, grip: &W, dir: Direction) {
self.h
.scroll_bar(&self.id, grip.id_ref(), track_rect, grip.rect(), dir);
}
pub fn slider<W: Tile>(&mut self, track_rect: Rect, grip: &W, dir: Direction) {
self.h
.slider(&self.id, grip.id_ref(), track_rect, grip.rect(), dir);
}
pub fn progress_bar(&mut self, rect: Rect, dir: Direction, value: f32) {
self.h.progress_bar(&self.id, rect, dir, value);
}
pub fn image(&mut self, rect: Rect, id: ImageId) {
self.h.image(id, rect);
}
}
#[autoimpl(for<H: trait + ?Sized> Box<H>)]
pub trait ThemeDraw {
fn components(&mut self) -> (&dyn ThemeSize, &mut dyn Draw, &mut EventState);
fn colors(&self) -> &ColorsLinear;
fn draw_rounded(&mut self) -> Option<&mut dyn DrawRounded>;
fn new_pass<'a>(
&mut self,
rect: Rect,
offset: Offset,
class: PassType,
f: Box<dyn FnOnce(&mut dyn ThemeDraw) + 'a>,
);
fn get_clip_rect(&mut self) -> Rect;
fn event_state_overlay(&mut self);
fn frame(&mut self, id: &Id, rect: Rect, style: FrameStyle, bg: Background);
fn separator(&mut self, rect: Rect);
fn selection(&mut self, rect: Rect, style: SelectionStyle);
fn text(&mut self, id: &Id, pos: Coord, rect: Rect, text: &TextDisplay, color: Option<Rgba>);
fn text_effects(
&mut self,
id: &Id,
pos: Coord,
rect: Rect,
text: &TextDisplay,
colors: &[Rgba],
effects: &[Effect],
);
fn text_selected_range(
&mut self,
id: &Id,
pos: Coord,
rect: Rect,
text: &TextDisplay,
range: Range<usize>,
);
fn text_cursor(&mut self, id: &Id, pos: Coord, rect: Rect, text: &TextDisplay, byte: usize);
fn check_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option<Instant>);
fn radio_box(&mut self, id: &Id, rect: Rect, checked: bool, last_change: Option<Instant>);
fn mark(&mut self, id: &Id, rect: Rect, style: MarkStyle);
fn scroll_bar(&mut self, id: &Id, grip_id: &Id, rect: Rect, grip_rect: Rect, dir: Direction);
fn slider(&mut self, id: &Id, grip_id: &Id, rect: Rect, grip_rect: Rect, dir: Direction);
fn progress_bar(&mut self, id: &Id, rect: Rect, dir: Direction, value: f32);
fn image(&mut self, id: ImageId, rect: Rect);
}
#[cfg(test)]
mod test {
use super::*;
fn _draw_ext(mut draw: DrawCx) {
let _scale = draw.size_cx().scale_factor();
let text = crate::theme::Text::new("sample", TextClass::Label, false);
draw.text_with_selection(Coord::ZERO, Rect::ZERO, &text, 0..6)
}
}