OneLauncher-Cracked/packages/oneclient_app/src/components/tab_bar.rs

242 lines
6.6 KiB
Rust

use freya::prelude::*;
use crate::theme::colors;
pub struct TabItem {
label: String,
active: bool,
count: Option<String>,
on_press: Option<EventHandler<Event<PressEventData>>>,
}
impl TabItem {
pub fn new(label: impl Into<String>, active: bool) -> Self {
Self {
label: label.into(),
active,
count: None,
on_press: None,
}
}
#[allow(dead_code)]
pub fn count_text(mut self, text: impl Into<String>) -> Self {
self.count = Some(text.into());
self
}
pub fn on_press(mut self, handler: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
self.on_press = Some(handler.into());
self
}
}
pub struct TabBar {
tabs: Vec<TabItem>,
width: Size,
height: Size,
spacing: f32,
font_size: f32,
}
impl Default for TabBar {
fn default() -> Self {
Self::new()
}
}
impl TabBar {
pub fn new() -> Self {
Self {
tabs: Vec::new(),
width: Size::fill(),
height: Size::fill(),
spacing: 24.,
font_size: 12.,
}
}
pub fn tabs(mut self, tabs: impl IntoIterator<Item = TabItem>) -> Self {
self.tabs = tabs.into_iter().collect();
self
}
pub fn width(mut self, width: Size) -> Self {
self.width = width;
self
}
pub fn height(mut self, height: Size) -> Self {
self.height = height;
self
}
pub fn spacing(mut self, spacing: f32) -> Self {
self.spacing = spacing;
self
}
pub fn font_size(mut self, font_size: f32) -> Self {
self.font_size = font_size;
self
}
}
fn tab_label(text: &str, font_size: f32, active: bool) -> impl IntoElement {
const ACTIVE_WEIGHT: FontWeight = FontWeight::MEDIUM;
rect()
.center()
.content(Content::Fit)
.child(
label()
.text(text.to_string())
.font_size(font_size)
.font_weight(ACTIVE_WEIGHT)
.max_lines(1)
.color(Color::TRANSPARENT)
.text_align(TextAlign::Center),
)
.child(
rect()
.position(
Position::new_absolute()
.top(0.)
.left(0.)
.right(0.)
.bottom(0.),
)
.center()
.child(
label()
.text(text.to_string())
.font_size(font_size)
.font_weight(if active {
ACTIVE_WEIGHT
} else {
FontWeight::NORMAL
})
.max_lines(1)
.color(colors::fg_primary())
.text_align(TextAlign::Center),
),
)
}
fn count_pill(count: &str, font_size: f32) -> impl IntoElement {
rect()
.center()
.padding(Gaps::new_symmetric(1., 6.))
.corner_radius(CornerRadius::new_all(10.))
.background(colors::component_bg())
.child(
label()
.text(count.to_string())
.font_size((font_size - 3.).max(8.))
.color(colors::fg_secondary()),
)
}
fn min_tab_width(text: &str, font_size: f32) -> f32 {
text.chars().count() as f32 * font_size * 0.62
}
impl IntoElement for TabBar {
fn into_element(self) -> Element {
let font_size = self.font_size;
let overflows = self.width == Size::auto();
let content = if overflows {
Content::Normal
} else {
Content::Fit
};
rect()
.horizontal()
.width(self.width)
.height(self.height)
.spacing(self.spacing)
.cross_align(Alignment::Center)
.content(content)
.children(self.tabs.into_iter().map(|tab| {
let min_width = overflows.then(|| min_tab_width(&tab.label, font_size));
TabButton {
label: tab.label,
active: tab.active,
count: tab.count,
font_size,
min_width,
on_press: tab.on_press,
}
.into_element()
}))
.into_element()
}
}
#[derive(PartialEq)]
struct TabButton {
label: String,
active: bool,
count: Option<String>,
font_size: f32,
min_width: Option<f32>,
on_press: Option<EventHandler<Event<PressEventData>>>,
}
impl Component for TabButton {
fn render(&self) -> impl IntoElement {
let a11y_id = use_a11y();
let focus = use_focus(a11y_id);
let active = self.active;
let font_size = self.font_size;
let underline_on = active || focus().is_focused();
let mut el = rect()
.vertical()
.content(Content::Fit)
.cross_align(Alignment::Center)
.map(self.min_width, |el, w| el.min_width(Size::px(w)))
.a11y_id(a11y_id)
.a11y_focusable(true)
.a11y_role(AccessibilityRole::Button)
.cursor(CursorIcon::Pointer);
if let Some(handler) = self.on_press.clone() {
el = el.on_press(move |e| handler.call(e));
}
el.child(
rect()
.horizontal()
.cross_align(Alignment::Center)
.spacing(6.)
.content(Content::Fit)
.child(
rect()
.vertical()
.content(Content::Fit)
.child(tab_label(&self.label, font_size, active))
.child(
rect()
.height(Size::px(1.5))
.width(Size::fill_minimum())
.margin(Gaps::new_symmetric(0., 4.0))
.corner_radius(CornerRadius::new_all(2.))
.background(if underline_on {
colors::fg_primary()
} else {
Color::TRANSPARENT
}),
),
)
.maybe_child(
self.count
.as_ref()
.map(|c| count_pill(c, font_size).into_element()),
),
)
}
}