feat: dropdown full-width click, gradient bg, pointing-hand cursor

This commit is contained in:
Erkan IŞIK
2026-06-15 02:34:26 +03:00
parent a48120b039
commit 25226c0f4f
5 changed files with 176 additions and 104 deletions
+24 -10
View File
@@ -114,21 +114,35 @@ pub fn language_selector_ui(ui: &mut eframe::egui::Ui, state: &mut crate::instal
let item_height = 28.0; let item_height = 28.0;
// Eleman kutularını sarmalayarak tam genişlikte tıklanabilir yapıyoruz // Eleman kutularını sarmalayarak tam genişlikte tıklanabilir yapıyoruz
ui.add_sized([available_width, item_height], |ui: &mut egui::Ui| { let (rect, item_resp) = ui.allocate_exact_size(
egui::vec2(available_width, item_height),
egui::Sense::click(),
);
{
use egui::epaint::Mesh;
let top = egui::Color32::from_rgb(249, 249, 249);
let bot = egui::Color32::from_rgb(233, 233, 233);
let mut mesh = Mesh::default();
let idx = mesh.vertices.len() as u32;
mesh.vertices.push(egui::epaint::Vertex { pos: egui::pos2(rect.left(), rect.top()), uv: egui::pos2(0.0, 0.0), color: top });
mesh.vertices.push(egui::epaint::Vertex { pos: egui::pos2(rect.right(), rect.top()), uv: egui::pos2(0.0, 0.0), color: top });
mesh.vertices.push(egui::epaint::Vertex { pos: egui::pos2(rect.right(), rect.bottom()), uv: egui::pos2(0.0, 0.0), color: bot });
mesh.vertices.push(egui::epaint::Vertex { pos: egui::pos2(rect.left(), rect.bottom()), uv: egui::pos2(0.0, 0.0), color: bot });
mesh.indices.extend_from_slice(&[idx, idx + 1, idx + 2, idx + 2, idx + 3, idx]);
ui.painter().add(egui::Shape::mesh(mesh));
}
ui.put(rect, |ui: &mut egui::Ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add_space(6.0); // İç sol boşluk ui.add_space(6.0);
ui.add(eframe::egui::Image::new(image_source.clone()).max_width(18.0)); ui.add(eframe::egui::Image::new(image_source.clone()).max_width(18.0));
ui.add_space(4.0); ui.add_space(4.0);
ui.label(*label);
let mut current_lang = state.language.clone();
let resp = ui.selectable_value(&mut current_lang, code.to_string(), *label);
if resp.clicked() {
state.language = code.to_string();
}
resp
}).response }).response
}); });
if item_resp.clicked() {
state.language = code.to_string();
}
item_resp.on_hover_cursor(egui::CursorIcon::PointingHand);
} }
}); });
}); });
+8 -9
View File
@@ -471,18 +471,18 @@ impl InstallerStep for WelcomeStep {
// Rescue mode butonu // Rescue mode butonu
ui.add_space(10.0); ui.add_space(10.0);
ui.allocate_ui( egui::Frame::none()
egui::vec2(ui.available_width(), 30.0), // Genişlik tam, Yükseklik sadece 24px .rounding(egui::Rounding::same(6.0))
|ui| { .inner_margin(egui::Margin::symmetric(8.0, 4.0))
// Şimdi dikeyde ortalayarak soldan sağa diziyoruz .show(ui, |ui| {
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { ui.horizontal(|ui| {
let rescue_available = detect_existing_linux(); let rescue_available = detect_existing_linux();
if rescue_available { if rescue_available {
if theme::secondary_button(ui, &t!("rescue_button")).clicked() { if theme::light_gray_btn(ui, &t!("rescue_button")).clicked() {
state.rescue_mode = true; state.rescue_mode = true;
} }
if theme::secondary_button(ui, &t!("recheck")).clicked() { if theme::light_gray_btn(ui, &t!("recheck")).clicked() {
self.checked = false; self.checked = false;
self.on_enter(state); self.on_enter(state);
} }
@@ -494,8 +494,7 @@ impl InstallerStep for WelcomeStep {
); );
} }
}); });
} });
);
+138
View File
@@ -0,0 +1,138 @@
use eframe::egui::{self, Color32, FontId, Rounding, Stroke, Vec2};
fn lerp_color(a: Color32, b: Color32, t: f32) -> Color32 {
Color32::from_rgba_premultiplied(
(a.r() as f32 + (b.r() as f32 - a.r() as f32) * t) as u8,
(a.g() as f32 + (b.g() as f32 - a.g() as f32) * t) as u8,
(a.b() as f32 + (b.b() as f32 - a.b() as f32) * t) as u8,
(a.a() as f32 + (b.a() as f32 - a.a() as f32) * t) as u8,
)
}
fn gradient_button(ui: &mut egui::Ui, text: &str, top_color: Color32, bottom_color: Color32, text_color: Color32, hover_top: Color32, hover_bottom: Color32, border_color: Color32) -> egui::Response {
let rounding = Rounding::same(6.0);
let min_size = Vec2::new(100.0, 32.0);
let padding = Vec2::new(12.0, 6.0);
let font_id = ui.style().text_styles.get(&egui::TextStyle::Button)
.cloned().unwrap_or(FontId::proportional(14.0));
let galley = ui.painter().layout_no_wrap(
text.to_string(),
font_id,
text_color,
);
let size = Vec2::new(
min_size.x.max(galley.size().x + 2.0 * padding.x),
min_size.y.max(galley.size().y + 2.0 * padding.y),
);
let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click());
let n_strips = 16;
for i in 0..n_strips {
let t = i as f32 / n_strips as f32;
let t2 = (i + 1) as f32 / n_strips as f32;
let c = lerp_color(top_color, bottom_color, (t + t2) * 0.5);
let y0 = rect.top() + rect.height() * t;
let y1 = rect.top() + rect.height() * t2;
let strip_rect = egui::Rect::from_min_max(
egui::pos2(rect.left(), y0),
egui::pos2(rect.right(), y1),
);
ui.painter().rect(strip_rect, Rounding::ZERO, c, Stroke::NONE);
}
if response.hovered() {
for i in 0..n_strips {
let t = i as f32 / n_strips as f32;
let t2 = (i + 1) as f32 / n_strips as f32;
let c = lerp_color(hover_top, hover_bottom, (t + t2) * 0.5);
let y0 = rect.top() + rect.height() * t;
let y1 = rect.top() + rect.height() * t2;
let strip_rect = egui::Rect::from_min_max(
egui::pos2(rect.left(), y0),
egui::pos2(rect.right(), y1),
);
ui.painter().rect(strip_rect, Rounding::ZERO, c, Stroke::NONE);
}
}
ui.painter().rect(rect, rounding, Color32::TRANSPARENT, Stroke::new(1.0, border_color));
ui.painter().galley(
egui::pos2(rect.center().x - galley.size().x / 2.0, rect.center().y - galley.size().y / 2.0),
galley,
text_color,
);
if let Some(cursor) = ui.visuals().interact_cursor {
if response.hovered() {
ui.ctx().set_cursor_icon(cursor);
}
}
response
}
pub fn primary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
gradient_button(ui, text,
Color32::from_rgb(50, 130, 255),
Color32::from_rgb(13, 110, 253),
Color32::WHITE,
Color32::from_rgb(70, 150, 255),
Color32::from_rgb(30, 130, 255),
Color32::from_rgb(13, 110, 253))
}
pub fn secondary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
gradient_button(ui, text,
Color32::from_rgb(130, 140, 150),
Color32::from_rgb(100, 110, 120),
Color32::from_rgb(158, 158, 175),
Color32::from_rgb(150, 160, 170),
Color32::from_rgb(120, 130, 140),
Color32::from_rgb(100, 110, 120))
}
pub fn danger_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
gradient_button(ui, text,
Color32::from_rgb(240, 83, 99),
Color32::from_rgb(200, 40, 60),
Color32::WHITE,
Color32::from_rgb(255, 100, 120),
Color32::from_rgb(220, 60, 80),
Color32::from_rgb(200, 40, 60))
}
#[allow(dead_code)]
pub fn white_grd_btn(ui: &mut egui::Ui, text: &str) -> egui::Response {
gradient_button(ui, text,
Color32::from_rgb(255, 255, 255),
Color32::from_rgb(246, 246, 246),
Color32::from_rgb(102, 102, 102),
Color32::from_rgb(246, 246, 246),
Color32::from_rgb(255, 255, 255),
Color32::from_rgb(177, 177, 177))
}
#[allow(dead_code)]
pub fn btn_dark_blue(ui: &mut egui::Ui, text: &str) -> egui::Response {
gradient_button(ui, text,
Color32::from_rgb(99, 184, 238),
Color32::from_rgb(70, 140, 207),
Color32::WHITE,
Color32::from_rgb(70, 140, 207),
Color32::from_rgb(99, 184, 238),
Color32::from_rgb(56, 102, 163))
}
#[allow(dead_code)]
pub fn light_gray_btn(ui: &mut egui::Ui, text: &str) -> egui::Response {
gradient_button(ui, text,
Color32::from_rgb(249, 249, 249), // üst: #f9f9f9
Color32::from_rgb(233, 233, 233), // alt: #e9e9e9
Color32::from_rgb(102, 102, 102), // metin: #666666
Color32::from_rgb(233, 233, 233), // hover üst (ters)
Color32::from_rgb(249, 249, 249), // hover alt (ters)
Color32::from_rgb(227, 195, 227)) // border: #e3c7e3
}
+1
View File
@@ -1,4 +1,5 @@
pub mod theme; pub mod theme;
pub mod buttons;
pub mod slideshow; pub mod slideshow;
pub mod error_screen; pub mod error_screen;
+5 -85
View File
@@ -15,6 +15,8 @@ use std::sync::atomic::{AtomicU32, Ordering};
static BG_DARK: AtomicU32 = AtomicU32::new(0x121214); static BG_DARK: AtomicU32 = AtomicU32::new(0x121214);
static BG_PANEL: AtomicU32 = AtomicU32::new(0x1A1A1E); static BG_PANEL: AtomicU32 = AtomicU32::new(0x1A1A1E);
static BG_WIDGET: AtomicU32 = AtomicU32::new(0x242428); static BG_WIDGET: AtomicU32 = AtomicU32::new(0x242428);
static BORDER: AtomicU32 = AtomicU32::new(0x323238); static BORDER: AtomicU32 = AtomicU32::new(0x323238);
static TEXT: AtomicU32 = AtomicU32::new(0xF3F3F5); static TEXT: AtomicU32 = AtomicU32::new(0xF3F3F5);
static TEXT_DIM: AtomicU32 = AtomicU32::new(0x9E9EAF); static TEXT_DIM: AtomicU32 = AtomicU32::new(0x9E9EAF);
@@ -284,96 +286,14 @@ fn build_style() -> egui::Style {
] ]
.into(); .into();
style.visuals.interact_cursor = Some(egui::CursorIcon::PointingHand);
style style
} }
// ─── Yardımcı widget'lar ───────────────────────────────────────── // ─── Yardımcı widget'lar ─────────────────────────────────────────
fn lerp_color(a: Color32, b: Color32, t: f32) -> Color32 { pub use super::buttons::*;
Color32::from_rgba_premultiplied(
(a.r() as f32 + (b.r() as f32 - a.r() as f32) * t) as u8,
(a.g() as f32 + (b.g() as f32 - a.g() as f32) * t) as u8,
(a.b() as f32 + (b.b() as f32 - a.b() as f32) * t) as u8,
(a.a() as f32 + (b.a() as f32 - a.a() as f32) * t) as u8,
)
}
fn gradient_button(ui: &mut egui::Ui, text: &str, top_color: Color32, bottom_color: Color32, text_color: Color32) -> egui::Response {
let rounding = Rounding::same(6.0);
let min_size = Vec2::new(100.0, 32.0);
let padding = Vec2::new(12.0, 6.0);
let font_id = ui.style().text_styles.get(&egui::TextStyle::Button)
.cloned().unwrap_or(FontId::proportional(14.0));
let galley = ui.painter().layout_no_wrap(
text.to_string(),
font_id,
text_color,
);
let size = Vec2::new(
min_size.x.max(galley.size().x + 2.0 * padding.x),
min_size.y.max(galley.size().y + 2.0 * padding.y),
);
let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click());
// Gradient arka plan (16 yatay şerit)
let n_strips = 16;
for i in 0..n_strips {
let t = i as f32 / n_strips as f32;
let t2 = (i + 1) as f32 / n_strips as f32;
let c = lerp_color(top_color, bottom_color, (t + t2) * 0.5);
let y0 = rect.top() + rect.height() * t;
let y1 = rect.top() + rect.height() * t2;
let strip_rect = egui::Rect::from_min_max(
egui::pos2(rect.left(), y0),
egui::pos2(rect.right(), y1),
);
ui.painter().rect(strip_rect, Rounding::ZERO, c, Stroke::NONE);
}
// Yuvarlak köşe efekti için border
ui.painter().rect(rect, rounding, Color32::TRANSPARENT, Stroke::new(1.0, bottom_color));
// Hover efekti
if response.hovered() {
ui.painter().rect(rect, rounding, Color32::WHITE.linear_multiply(0.10), Stroke::NONE);
}
// Metin
ui.painter().galley(
egui::pos2(rect.center().x - galley.size().x / 2.0, rect.center().y - galley.size().y / 2.0),
galley,
text_color,
);
response
}
/// Ana eylem butonu (mavi gradient).
pub fn primary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
gradient_button(ui, text,
Color32::from_rgb(50, 130, 255), // üst: açık mavi
Color32::from_rgb(13, 110, 253), // alt: koyu mavi
Color32::WHITE)
}
/// İkincil buton (gri gradient).
pub fn secondary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
gradient_button(ui, text,
Color32::from_rgb(130, 140, 150), // üst: açık gri
Color32::from_rgb(100, 110, 120), // alt: koyu gri
c_text_dim())
}
/// İptal/Danger butonu (kırmızı gradient).
pub fn danger_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
gradient_button(ui, text,
Color32::from_rgb(240, 83, 99), // üst: açık kırmızı
Color32::from_rgb(200, 40, 60), // alt: koyu kırmızı
Color32::WHITE)
}
/// Bölüm başlığı — büyük, açık renkli, alt çizgisiz. /// Bölüm başlığı — büyük, açık renkli, alt çizgisiz.
pub fn section_heading(ui: &mut egui::Ui, text: &str) { pub fn section_heading(ui: &mut egui::Ui, text: &str) {