Menu
cosmic-mobile
publicLatest change dc9bba1298897771c885931ff76e1a0995d3652b - Fix iOS control details: delete glyph, switches, pill zero, hidden scrollbar, truncated rows by AkurAI Build
//! iOS 26 style upgrades applied to the existing system controls.
use eframe::egui::*;
pub const BLUE: Color32 = Color32::from_rgb(10, 132, 255);
pub const GREEN: Color32 = Color32::from_rgb(48, 209, 88);
pub const RED: Color32 = Color32::from_rgb(255, 69, 58);
pub const ORANGE: Color32 = Color32::from_rgb(255, 159, 10);
pub const BACKGROUND: Color32 = Color32::from_rgb(0, 0, 0);
pub const CARD: Color32 = Color32::from_rgb(28, 28, 30);
pub const FIELD: Color32 = Color32::from_rgb(44, 44, 46);
pub const KEY: Color32 = Color32::from_rgb(51, 51, 51);
pub const KEY_LIGHT: Color32 = Color32::from_rgb(165, 165, 165);
pub const SECONDARY: Color32 = Color32::from_rgb(142, 142, 147);
pub fn back(ui: &mut Ui, label: &str) -> bool {
ui.add(
Button::new(RichText::new(format!("‹ {label}")).size(17.).color(BLUE))
.frame(false)
.min_size(vec2(88., 44.)),
)
.clicked()
}
pub fn large_title(ui: &mut Ui, text: &str) {
ui.add_space(4.);
ui.label(RichText::new(text).size(34.).strong());
ui.add_space(12.);
}
pub fn caption(ui: &mut Ui, text: &str) {
ui.label(
RichText::new(text.to_uppercase())
.size(13.)
.color(SECONDARY),
);
ui.add_space(6.);
}
pub fn card(ui: &mut Ui, add: impl FnOnce(&mut Ui)) {
Frame::new()
.fill(CARD)
.corner_radius(16)
.inner_margin(Margin::symmetric(16, 4))
.show(ui, |ui| {
ui.set_width(ui.available_width());
add(ui);
});
ui.add_space(16.);
}
pub fn row(ui: &mut Ui, title: &str, detail: &str) {
ui.allocate_ui_with_layout(
vec2(ui.available_width(), 44.),
Layout::left_to_right(Align::Center),
|ui| {
ui.label(RichText::new(title).size(17.));
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
ui.add(Label::new(RichText::new(detail).size(17.).color(SECONDARY)).truncate());
});
},
);
}
/// Backspace glyph painted with strokes; default fonts lack U+232B.
pub fn delete_key(ui: &mut Ui, size: f32) -> Response {
let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::click());
response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, true, "Delete"));
let c = rect.center();
let s = Stroke::new(2.5_f32, KEY_LIGHT);
let (w, h) = (size * 0.26, size * 0.17);
ui.painter().add(Shape::closed_line(
vec![
c + vec2(-w, 0.),
c + vec2(-w * 0.45, -h),
c + vec2(w, -h),
c + vec2(w, h),
c + vec2(-w * 0.45, h),
],
s,
));
let x = size * 0.07;
ui.painter()
.line_segment([c + vec2(-x * 0.6, -x), c + vec2(x * 1.6, x)], s);
ui.painter()
.line_segment([c + vec2(-x * 0.6, x), c + vec2(x * 1.6, -x)], s);
response
}
pub fn field(ui: &mut Ui, text: &mut String, hint: &str) -> Response {
Frame::new()
.fill(FIELD)
.corner_radius(12)
.inner_margin(Margin::symmetric(14, 0))
.show(ui, |ui| {
ui.add_sized(
[ui.available_width(), 48.],
TextEdit::singleline(text)
.hint_text(hint)
.font(FontId::proportional(17.))
.frame(false),
)
})
.inner
}
pub fn primary(ui: &mut Ui, label: &str, fill: Color32) -> bool {
ui.add_space(8.);
let clicked = ui
.add_sized(
[ui.available_width(), 50.],
Button::new(RichText::new(label).size(17.).strong())
.fill(fill)
.corner_radius(14),
)
.clicked();
ui.add_space(8.);
clicked
}
pub fn key(ui: &mut Ui, label: &str, size: f32, fill: Color32, text: Color32) -> Response {
let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::click());
response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, true, label));
let fill = if response.is_pointer_button_down_on() {
fill.gamma_multiply(1.4)
} else {
fill
};
ui.painter().circle_filled(rect.center(), size / 2., fill);
ui.painter().text(
rect.center(),
Align2::CENTER_CENTER,
label,
FontId::proportional(size * 0.42),
text,
);
response
}
pub fn toggle(ui: &mut Ui, on: &mut bool, title: &str) -> Response {
let mut response = ui
.allocate_ui_with_layout(
vec2(ui.available_width(), 44.),
Layout::left_to_right(Align::Center),
|ui| {
ui.label(RichText::new(title).size(17.));
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
let (rect, r) = ui.allocate_exact_size(vec2(51., 31.), Sense::click());
r.widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, true, *on, title));
let t = ui.ctx().animate_bool(r.id, *on);
let fill = Color32::from_rgb(57, 57, 60).lerp_to_gamma(GREEN, t);
ui.painter().rect_filled(rect, 15.5, fill);
let x = rect.left() + 15.5 + t * (rect.width() - 31.);
ui.painter()
.circle_filled(pos2(x, rect.center().y), 13.5, Color32::WHITE);
r
})
.inner
},
)
.inner;
if response.clicked() {
*on = !*on;
response.mark_changed();
}
response
}
pub fn pill(ui: &mut Ui, label: &str, width: f32, height: f32, fill: Color32) -> Response {
let (rect, response) = ui.allocate_exact_size(vec2(width, height), Sense::click());
response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, true, label));
let fill = if response.is_pointer_button_down_on() {
fill.gamma_multiply(1.4)
} else {
fill
};
ui.painter().rect_filled(rect, height / 2., fill);
ui.painter().text(
pos2(rect.left() + height / 2., rect.center().y),
Align2::CENTER_CENTER,
label,
FontId::proportional(height * 0.42),
Color32::WHITE,
);
response
}