use crate::text_layout::build_text_lines;
use crate::*;
fn baseline_aligned_top(rect: Recti, line_height: i32, baseline: i32) -> i32 {
if rect.height >= line_height {
return rect.y + (rect.height - line_height) / 2;
}
let baseline_center = rect.y + rect.height / 2;
let min_top = rect.y + rect.height - line_height;
let max_top = rect.y;
(baseline_center - baseline).clamp(min_top, max_top)
}
fn text_lines<'a>(text: &'a str, wrap: TextWrap, max_width: i32, font: FontId, atlas: &AtlasHandle) -> Vec<crate::text_layout::TextLine> {
let mut lines = build_text_lines(text, wrap, max_width, font, atlas);
if text.ends_with('\n') {
if let Some(last) = lines.last() {
if last.start == text.len() && last.end == text.len() {
lines.pop();
}
}
}
lines
}
#[derive(Clone)]
pub struct TextBlock {
pub text: String,
pub wrap: TextWrap,
pub font: FontChoice,
pub opt: WidgetOption,
pub bopt: WidgetBehaviourOption,
}
impl TextBlock {
pub fn new(text: impl Into<String>) -> Self {
Self::with_wrap(text, TextWrap::None)
}
pub fn with_wrap(text: impl Into<String>, wrap: TextWrap) -> Self {
Self {
text: text.into(),
wrap,
font: FontChoice::default(),
opt: WidgetOption::NO_INTERACT | WidgetOption::NO_FRAME,
bopt: WidgetBehaviourOption::NONE,
}
}
fn preferred_size_widget(&self, style: &Style, atlas: &AtlasHandle, avail: Dimensioni) -> Dimensioni {
if self.text.is_empty() {
return Dimensioni::new(0, 0);
}
let font = style.resolve_font_choice(self.font);
let line_height = atlas.get_font_height(font) as i32;
let max_width = if self.wrap == TextWrap::Word && avail.width > 0 {
avail.width.max(1)
} else {
i32::MAX / 4
};
let lines = text_lines(self.text.as_str(), self.wrap, max_width, font, atlas);
let width = lines.iter().map(|line| line.width).max().unwrap_or(0).max(0);
let height = line_height.saturating_mul((lines.len() as i32).max(1)).max(0);
Dimensioni::new(width, height)
}
fn handle_widget(&mut self, ctx: &mut WidgetCtx<'_>, _control: &ControlState) -> ResourceState {
if self.text.is_empty() {
return ResourceState::NONE;
}
let bounds = ctx.rect();
let font = ctx.style().resolve_font_choice(self.font);
let color = ctx.style().colors[ControlColor::Text as usize];
let line_height = ctx.atlas().get_font_height(font) as i32;
let baseline = ctx.atlas().get_font_baseline(font);
let max_width = if self.wrap == TextWrap::Word { bounds.width.max(1) } else { i32::MAX / 4 };
let lines = text_lines(self.text.as_str(), self.wrap, max_width, font, ctx.atlas());
ctx.push_clip_rect(bounds);
for (idx, line) in lines.iter().enumerate() {
let line_rect = rect(bounds.x, bounds.y + idx as i32 * line_height, bounds.width, line_height);
let line_top = baseline_aligned_top(line_rect, line_height, baseline);
let slice = &self.text[line.start..line.end];
if !slice.is_empty() {
ctx.draw_text(font, slice, vec2(line_rect.x, line_top), color);
}
}
ctx.pop_clip_rect();
ResourceState::NONE
}
}
implement_widget!(TextBlock, handle_widget, preferred_size_widget);
#[derive(Clone)]
pub struct ColorSwatch {
pub fill: Color,
pub label: String,
pub font: FontChoice,
pub opt: WidgetOption,
pub bopt: WidgetBehaviourOption,
}
impl ColorSwatch {
pub fn new(fill: Color) -> Self {
Self {
fill,
label: String::new(),
font: FontChoice::default(),
opt: WidgetOption::NO_INTERACT | WidgetOption::ALIGN_CENTER,
bopt: WidgetBehaviourOption::NONE,
}
}
fn preferred_size_widget(&self, style: &Style, atlas: &AtlasHandle, _avail: Dimensioni) -> Dimensioni {
let padding = style.padding.max(0);
let font = style.resolve_font_choice(self.font);
let label_width = if self.label.is_empty() {
0
} else {
atlas.get_text_size(font, self.label.as_str()).width.max(0)
};
let height = (atlas.get_font_height(font) as i32 + padding * 2).max(24);
Dimensioni::new((label_width + padding * 2).max(24), height)
}
fn handle_widget(&mut self, ctx: &mut WidgetCtx<'_>, _control: &ControlState) -> ResourceState {
let rect = ctx.rect();
ctx.draw_rect(rect, self.fill);
let border = ctx.style().colors[ControlColor::Border as usize];
ctx.draw_box(rect, border);
if !self.label.is_empty() {
let font = ctx.style().resolve_font_choice(self.font);
ctx.draw_control_text_with_font(font, self.label.as_str(), rect, ControlColor::Text, self.opt);
}
ResourceState::NONE
}
}
implement_widget!(ColorSwatch, handle_widget, preferred_size_widget);