use crate::{
core::{bindings::KeyPress, hooks::Hook, xconnection::XConn},
draw::{Color, DrawContext, DrawError, KeyboardControlled, Result, TextStyle, Widget},
};
#[derive(Clone, Debug, PartialEq)]
pub struct Text {
txt: String,
font: String,
point_size: i32,
fg: Color,
bg: Option<Color>,
padding: (f64, f64),
is_greedy: bool,
right_justified: bool,
extent: Option<(f64, f64)>,
require_draw: bool,
}
impl Text {
pub fn new(
txt: impl Into<String>,
style: &TextStyle,
is_greedy: bool,
right_justified: bool,
) -> Self {
Self {
txt: txt.into(),
font: style.font.clone(),
point_size: style.point_size,
fg: style.fg,
bg: style.bg,
padding: style.padding,
is_greedy,
right_justified,
extent: None,
require_draw: false,
}
}
pub fn get_text(&self) -> &String {
&self.txt
}
pub fn get_text_mut(&mut self) -> &mut String {
&mut self.txt
}
pub fn set_text(&mut self, txt: impl Into<String>) {
let new_text = txt.into();
if self.txt != new_text {
self.txt = new_text;
self.extent = None;
self.require_draw = true;
}
}
pub fn force_draw(&mut self) {
self.require_draw = true;
}
}
impl<X> Hook<X> for Text where X: XConn {}
impl Widget for Text {
fn draw(&mut self, ctx: &mut dyn DrawContext, _: usize, _: bool, w: f64, h: f64) -> Result<()> {
if let Some(color) = self.bg {
ctx.color(&color);
ctx.rectangle(0.0, 0.0, w, h);
}
let (ew, eh) = self.current_extent(ctx, h)?;
ctx.font(&self.font, self.point_size)?;
ctx.color(&self.fg);
let offset = w - ew;
let right_justify = self.right_justified && self.is_greedy && offset > 0.0;
if right_justify {
ctx.translate(offset, 0.0);
ctx.text(&self.txt, h - eh, self.padding)?;
ctx.translate(-offset, 0.0);
} else {
ctx.text(&self.txt, h - eh, self.padding)?;
}
self.require_draw = false;
Ok(())
}
fn current_extent(&mut self, ctx: &mut dyn DrawContext, _h: f64) -> Result<(f64, f64)> {
match self.extent {
Some(extent) => Ok(extent),
None => {
let (l, r) = self.padding;
ctx.font(&self.font, self.point_size)?;
let (w, h) = ctx.text_extent(&self.txt)?;
let extent = (w + l + r, h);
self.extent = Some(extent);
Ok(extent)
}
}
}
fn require_draw(&self) -> bool {
self.require_draw
}
fn is_greedy(&self) -> bool {
self.is_greedy
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct LinesWithSelection {
lines: Vec<String>,
selected: usize,
n_lines: usize,
font: String,
point_size: i32,
padding: f64,
bg: Color,
fg: Color,
fg_sel: Color,
bg_sel: Color,
require_draw: bool,
extent: Option<(f64, f64)>,
greedy: bool,
}
impl LinesWithSelection {
pub fn new(
font: String,
point_size: i32,
padding: f64,
bg: Color,
fg: Color,
bg_sel: Color,
fg_sel: Color,
n_lines: usize,
greedy: bool,
) -> Self {
Self {
lines: vec![],
selected: 0,
n_lines,
font,
point_size,
padding,
bg,
fg,
bg_sel,
fg_sel,
require_draw: false,
extent: None,
greedy,
}
}
pub fn set_input(&mut self, lines: Vec<String>) -> Result<()> {
self.lines = lines;
self.selected = 0;
self.require_draw = true;
self.extent = None;
Ok(())
}
pub fn set_selected(&mut self, selected: usize) -> Result<()> {
if selected >= self.lines.len() {
return Err(DrawError::Raw(format!(
"index out of bounds: {} >= {}",
selected,
self.lines.len()
)));
}
self.selected = selected;
self.require_draw = true;
Ok(())
}
pub fn set_n_lines(&mut self, n_lines: usize) {
self.n_lines = n_lines;
self.require_draw = true;
self.extent = None;
}
pub fn selected(&self) -> Option<&str> {
self.lines.get(self.selected).map(|s| s.as_ref())
}
pub fn selected_index(&self) -> usize {
self.selected
}
pub fn lines(&self) -> &Vec<String> {
&self.lines
}
}
impl<X> Hook<X> for LinesWithSelection where X: XConn {}
impl Widget for LinesWithSelection {
fn draw(
&mut self,
ctx: &mut dyn DrawContext,
_screen: usize,
_screen_has_focus: bool,
w: f64,
h: f64,
) -> Result<()> {
ctx.color(&self.bg);
ctx.rectangle(0.0, 0.0, w, h);
ctx.font(&self.font, self.point_size)?;
ctx.translate(self.padding, self.padding);
let block = self.selected / self.n_lines;
self.lines
.iter()
.enumerate()
.skip(block * self.n_lines)
.take(self.n_lines)
.try_for_each(|(ix, line)| -> Result<()> {
let (_, lh) = ctx.text_extent(line)?;
let fg = if ix == self.selected {
ctx.color(&self.bg_sel);
ctx.rectangle(0.0, 0.0, w + self.padding * 2.0, lh);
self.fg_sel
} else {
self.fg
};
ctx.color(&fg);
ctx.text(line, 0.0, (self.padding, self.padding))?;
ctx.translate(0.0, lh);
Ok(())
})?;
Ok(())
}
fn current_extent(&mut self, ctx: &mut dyn DrawContext, _h: f64) -> Result<(f64, f64)> {
match self.extent {
Some(extent) => Ok(extent),
None => {
let mut height = 0.0;
let mut w_max = 0.0;
for (i, line) in self.lines.iter().enumerate() {
ctx.font(&self.font, self.point_size)?;
let (w, h) = ctx.text_extent(line)?;
w_max = if w > w_max { w } else { w_max };
if i < self.n_lines {
height += h;
}
}
let ext = (w_max + self.padding * 2.0, height + self.padding * 2.0);
self.extent = Some(ext);
Ok(ext)
}
}
}
fn require_draw(&self) -> bool {
self.require_draw
}
fn is_greedy(&self) -> bool {
self.greedy
}
}
impl KeyboardControlled for LinesWithSelection {
fn handle_keypress(&mut self, k: KeyPress) -> Result<Option<KeyPress>> {
match k {
KeyPress::Up => {
if self.selected > 0 {
self.selected -= 1
}
}
KeyPress::Down => {
if !self.lines.is_empty() && self.selected < self.lines.len() - 1 {
self.selected += 1
}
}
_ => return Ok(Some(k)),
}
Ok(None)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct InputBox {
txt: Text,
}
impl InputBox {
pub fn new(style: &TextStyle, is_greedy: bool, right_justified: bool) -> Self {
Self {
txt: Text::new("", style, is_greedy, right_justified),
}
}
pub fn get_text(&self) -> &String {
self.txt.get_text()
}
pub fn get_text_mut(&mut self) -> &mut String {
self.txt.get_text_mut()
}
pub fn set_text(&mut self, txt: impl Into<String>) {
self.txt.set_text(txt);
}
}
impl<X> Hook<X> for InputBox where X: XConn {}
impl Widget for InputBox {
fn draw(&mut self, ctx: &mut dyn DrawContext, s: usize, f: bool, w: f64, h: f64) -> Result<()> {
self.txt.draw(ctx, s, f, w, h)
}
fn current_extent(&mut self, ctx: &mut dyn DrawContext, h: f64) -> Result<(f64, f64)> {
self.txt.current_extent(ctx, h)
}
fn require_draw(&self) -> bool {
self.txt.require_draw()
}
fn is_greedy(&self) -> bool {
self.txt.is_greedy()
}
}
impl KeyboardControlled for InputBox {
fn handle_keypress(&mut self, k: KeyPress) -> Result<Option<KeyPress>> {
match k {
KeyPress::Backspace => {
let s = self.get_text_mut();
if !s.is_empty() {
s.pop();
}
}
KeyPress::Utf8(c) => self.get_text_mut().push_str(&c),
_ => return Ok(Some(k)),
}
Ok(None)
}
}