forked from pisilinux-rs/yali-rs
refactor: extract ComboBox styling into dedicated module, add location step
- Create src/ui/combobox.rs with helper functions (show_combo_box, selectable_value, selectable_value_custom) - Create src/ui/combobox_stil.rs with ComboBox visual constants (weak_bg_fill, text/border colors) - Move theme.rs ComboBox visuals (weak_bg_fill, fg_stroke, bg_stroke) into combobox_stil::apply_visuals - Move LocationStep from installer.rs to src/steps/location.rs - Re-register LocationStep in steps/mod.rs - Update all ComboBox usages across steps (bootloader, display_manager, partition, funct) to use theme::show_combo_box / selectable_value - Replace manual mesh drawing in language selector with combobox helper - Add region/timezone i18n keys - Vertical-align region/zone labels to match ComboBox height
This commit is contained in:
@@ -401,6 +401,7 @@ job_media_check = "Verifying installation media"
|
||||
# ── CLI / Auto-install ─────────────────────────────────────
|
||||
hostname = "Hostname"
|
||||
timezone = "Timezone"
|
||||
region = "Region"
|
||||
username = "Username"
|
||||
|
||||
# ── Slideshow (fallback) ───────────────────────────────────
|
||||
|
||||
@@ -422,6 +422,7 @@ lvm_lv_action = "İşlem"
|
||||
# ── CLI / Otomatik Kurulum ─────────────────────────────────
|
||||
hostname = "Bilgisayar adı"
|
||||
timezone = "Zaman dilimi"
|
||||
region = "Bölge"
|
||||
username = "Kullanıcı adı"
|
||||
|
||||
# ── Slayt Gösterisi (yedek) ────────────────────────────────
|
||||
|
||||
+8
-35
@@ -101,36 +101,9 @@ pub fn language_selector_ui(ui: &mut eframe::egui::Ui, state: &mut crate::instal
|
||||
("en", "English", eframe::egui::include_image!("../assets/flags/us.svg")),
|
||||
];
|
||||
|
||||
// Standart egui makrosu - Tema motoru sayesinde otomatik olarak oval ve renk geçişli hissi verir
|
||||
eframe::egui::ComboBox::from_id_source("language_select_combo")
|
||||
.selected_text(current_lang_label)
|
||||
.width(available_width) // Seçim kutusunu yatayda genişletir
|
||||
.show_ui(ui, |ui| {
|
||||
// Açılan alt listenin genişliğini de üst butonla eşitliyoruz (HTML SelectBox Mantığı)
|
||||
ui.set_min_width(available_width);
|
||||
ui.set_max_width(available_width);
|
||||
|
||||
for (code, label, image_source) in languages.iter() {
|
||||
let item_height = 28.0;
|
||||
|
||||
// Eleman kutularını sarmalayarak tam genişlikte tıklanabilir yapıyoruz
|
||||
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));
|
||||
}
|
||||
crate::ui::theme::show_combo_box(ui, "language_select_combo", current_lang_label, available_width, |ui, w| {
|
||||
for (code, label, image_source) in languages.iter() {
|
||||
let resp = crate::ui::theme::selectable_value_custom(ui, w, |ui, rect| {
|
||||
ui.put(rect, |ui: &mut egui::Ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(6.0);
|
||||
@@ -139,11 +112,11 @@ pub fn language_selector_ui(ui: &mut eframe::egui::Ui, state: &mut crate::instal
|
||||
ui.label(*label);
|
||||
}).response
|
||||
});
|
||||
if item_resp.clicked() {
|
||||
state.language = code.to_string();
|
||||
}
|
||||
item_resp.on_hover_cursor(egui::CursorIcon::PointingHand);
|
||||
});
|
||||
if resp.clicked() {
|
||||
state.language = code.to_string();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -510,212 +510,7 @@ impl InstallerStep for WelcomeStep {
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// LOCATION STEP
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
pub struct LocationStep {
|
||||
timezones: Vec<String>,
|
||||
geoip_done: bool,
|
||||
datetime_init_done: bool,
|
||||
}
|
||||
|
||||
impl Default for LocationStep {
|
||||
fn default() -> Self {
|
||||
let tz_list = vec![
|
||||
"Africa/Cairo", "America/Argentina/Buenos_Aires", "America/New_York",
|
||||
"America/Sao_Paulo", "Asia/Baku", "Asia/Dubai", "Asia/Istanbul",
|
||||
"Asia/Tokyo", "Australia/Sydney", "Europe/Berlin", "Europe/Istanbul",
|
||||
"Europe/London", "Europe/Moscow", "Europe/Paris", "Europe/Rome",
|
||||
"Pacific/Auckland",
|
||||
].iter().map(|s| s.to_string()).collect();
|
||||
Self {
|
||||
timezones: tz_list,
|
||||
geoip_done: false,
|
||||
datetime_init_done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocationStep {
|
||||
fn geoip_lookup(state: &mut GlobalState) {
|
||||
let out = std::process::Command::new("curl")
|
||||
.args(["-s", "--connect-timeout", "3", "http://ip-api.com/json"])
|
||||
.output();
|
||||
if let Ok(o) = out {
|
||||
if let Ok(text) = String::from_utf8(o.stdout) {
|
||||
let extract = |key: &str| -> Option<String> {
|
||||
let pat = format!("\"{}\":\"", key);
|
||||
text.find(&pat).and_then(|pos| {
|
||||
let start = pos + pat.len();
|
||||
text[start..].find('"').map(|end| text[start..start+end].to_string())
|
||||
})
|
||||
};
|
||||
if let Some(country) = extract("countryCode") {
|
||||
match country.as_str() {
|
||||
"TR" => state.language = "tr".to_string(),
|
||||
"DE" => state.language = "de".to_string(),
|
||||
"FR" => state.language = "fr".to_string(),
|
||||
"IT" => state.language = "it".to_string(),
|
||||
"ES" => state.language = "es".to_string(),
|
||||
"RU" => state.language = "ru".to_string(),
|
||||
"NL" => state.language = "nl".to_string(),
|
||||
"PL" => state.language = "pl".to_string(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(tz) = extract("timezone") {
|
||||
if !tz.is_empty() {
|
||||
state.timezone = tz;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_from_system(&mut self, state: &mut GlobalState) {
|
||||
use chrono::{Datelike, Timelike};
|
||||
let now_str = chrono::Local::now().format("%Y-%m-%d %H:%M").to_string();
|
||||
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(&now_str, "%Y-%m-%d %H:%M") {
|
||||
state.manual_year = dt.year();
|
||||
state.manual_month = dt.month();
|
||||
state.manual_day = dt.day();
|
||||
state.manual_hour = dt.hour();
|
||||
state.manual_minute = dt.minute();
|
||||
}
|
||||
self.datetime_init_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl InstallerStep for LocationStep {
|
||||
fn name(&self) -> String {
|
||||
format!("{} / {}", t!("location"), t!("datetime"))
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state: &mut GlobalState) {
|
||||
if !self.geoip_done {
|
||||
Self::geoip_lookup(state);
|
||||
self.geoip_done = true;
|
||||
}
|
||||
if !self.datetime_init_done {
|
||||
self.init_from_system(state);
|
||||
}
|
||||
}
|
||||
|
||||
fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) {
|
||||
use crate::ui::theme;
|
||||
|
||||
// --- BÖLGE VE ZAMAN DİLİMİ (Region & Timezone) ---
|
||||
theme::section_heading(ui, &t!("location_title"));
|
||||
ui.label(
|
||||
egui::RichText::new(t!("location_description"))
|
||||
.color(theme::c_text_dim())
|
||||
.size(13.0),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(t!("location_title")).strong().color(theme::c_text()));
|
||||
|
||||
let current_tz = if state.timezone.is_empty() {
|
||||
"Europe/Istanbul".to_string()
|
||||
} else {
|
||||
state.timezone.clone()
|
||||
};
|
||||
if state.timezone.is_empty() {
|
||||
state.timezone = current_tz.clone();
|
||||
}
|
||||
|
||||
egui::ComboBox::from_id_source("timezone_select")
|
||||
.selected_text(¤t_tz)
|
||||
.width(280.0)
|
||||
.show_ui(ui, |ui| {
|
||||
for tz in &self.timezones {
|
||||
ui.selectable_value(&mut state.timezone, tz.clone(), tz);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(16.0);
|
||||
ui.separator();
|
||||
ui.add_space(16.0);
|
||||
|
||||
// --- TARİH VE SAAT AYARLARI (Date & Time) ---
|
||||
theme::section_heading(ui, &t!("datetime_title"));
|
||||
ui.label(
|
||||
egui::RichText::new(t!("datetime_description"))
|
||||
.color(theme::c_text_dim())
|
||||
.size(13.0),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
egui::Frame::group(ui.style())
|
||||
.fill(theme::c_bg_widget())
|
||||
.rounding(6.0)
|
||||
.inner_margin(egui::Margin::same(12.0))
|
||||
.show(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
egui::Grid::new("datetime_grid")
|
||||
.num_columns(2)
|
||||
.spacing([12.0, 10.0])
|
||||
.show(ui, |ui| {
|
||||
// NTP
|
||||
ui.label(egui::RichText::new(t!("datetime_ntp_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::Checkbox::new(&mut state.use_ntp, ""));
|
||||
let status = if state.use_ntp { t!("datetime_ntp_on") } else { t!("datetime_ntp_off") };
|
||||
ui.colored_label(
|
||||
if state.use_ntp { theme::c_success() } else { theme::c_error() },
|
||||
status,
|
||||
);
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
if !state.use_ntp {
|
||||
// Tarih
|
||||
ui.label(egui::RichText::new(t!("datetime_date_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(t!("datetime_year"));
|
||||
ui.add(egui::DragValue::new(&mut state.manual_year).clamp_range(2000..=2100).speed(1));
|
||||
ui.label(t!("datetime_month"));
|
||||
ui.add(egui::DragValue::new(&mut state.manual_month).clamp_range(1..=12).speed(1));
|
||||
ui.label(t!("datetime_day"));
|
||||
ui.add(egui::DragValue::new(&mut state.manual_day).clamp_range(1..=31).speed(1));
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
// Saat
|
||||
ui.label(egui::RichText::new(t!("datetime_time_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::DragValue::new(&mut state.manual_hour).clamp_range(0..=23).speed(1));
|
||||
ui.label(":");
|
||||
ui.add(egui::DragValue::new(&mut state.manual_minute).clamp_range(0..=59).speed(1));
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
if state.use_ntp {
|
||||
ui.label(
|
||||
egui::RichText::new(t!("datetime_ntp_info"))
|
||||
.size(11.0)
|
||||
.color(theme::c_text_dim()),
|
||||
);
|
||||
} else {
|
||||
ui.label(
|
||||
egui::RichText::new(t!("datetime_manual_info"))
|
||||
.size(11.0)
|
||||
.color(theme::c_text_dim()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete(&self, state: &GlobalState) -> bool {
|
||||
!state.timezone.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Mevcut bir Linux kurulumu olup olmadığını kontrol eder.
|
||||
/// Basitçe /mnt/etc/fstab varlığına veya blkid ile Linux bölümlerine bakar.
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ mod autoinstall;
|
||||
mod branding;
|
||||
mod funct;
|
||||
|
||||
use installer::{GlobalState, InstallerStep, WelcomeStep, LocationStep};
|
||||
use steps::{KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep, DisplayManagerStep};
|
||||
use installer::{GlobalState, InstallerStep, WelcomeStep};
|
||||
use steps::{LocationStep, KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep, DisplayManagerStep};
|
||||
use eframe::egui;
|
||||
use rust_i18n::t;
|
||||
|
||||
|
||||
+6
-11
@@ -1,6 +1,7 @@
|
||||
use eframe::egui;
|
||||
use rust_i18n::t;
|
||||
use crate::installer::{GlobalState, InstallerStep};
|
||||
use crate::ui::theme;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BootloaderStep {
|
||||
@@ -47,17 +48,11 @@ impl InstallerStep for BootloaderStep {
|
||||
} else {
|
||||
state.bootloader_device.clone()
|
||||
};
|
||||
egui::ComboBox::from_id_source("boot_device_select")
|
||||
.selected_text(¤t)
|
||||
.show_ui(ui, |ui| {
|
||||
for dev in &device_list {
|
||||
ui.selectable_value(
|
||||
&mut state.bootloader_device,
|
||||
dev.clone(),
|
||||
dev,
|
||||
);
|
||||
}
|
||||
});
|
||||
theme::show_combo_box(ui, "boot_device_select", current.as_str(), ui.available_width(), |ui, w| {
|
||||
for dev in &device_list {
|
||||
theme::selectable_value(ui, &mut state.bootloader_device, dev.clone(), dev.as_str(), w);
|
||||
}
|
||||
});
|
||||
ui.label(
|
||||
egui::RichText::new(t!("bootloader_device_hint"))
|
||||
.size(11.0)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use eframe::egui;
|
||||
use rust_i18n::t;
|
||||
use crate::installer::{GlobalState, InstallerStep};
|
||||
use crate::ui::theme;
|
||||
|
||||
const DM_OPTIONS: &[&str] = &["sddm", "lightdm", "gdm"];
|
||||
const DE_OPTIONS: &[&str] = &["plasma", "xfce", "gnome", "cinnamon", "budgie", "lxde", "lxqt", "mate", "lumina"];
|
||||
@@ -31,37 +32,26 @@ impl InstallerStep for DisplayManagerStep {
|
||||
.show(ui, |ui| {
|
||||
// Ekran yöneticisi
|
||||
ui.label(t!("display_manager_select"));
|
||||
egui::ComboBox::from_id_source("dm_combo")
|
||||
.selected_text(state.display_manager.as_str())
|
||||
.show_ui(ui, |ui| {
|
||||
for dm in DM_OPTIONS {
|
||||
ui.selectable_value(
|
||||
&mut state.display_manager,
|
||||
dm.to_string(),
|
||||
*dm,
|
||||
);
|
||||
}
|
||||
});
|
||||
let dm_current = state.display_manager.clone();
|
||||
theme::show_combo_box(ui, "dm_combo", dm_current.as_str(), ui.available_width(), |ui, w| {
|
||||
for dm in DM_OPTIONS {
|
||||
theme::selectable_value(ui, &mut state.display_manager, dm.to_string(), *dm, w);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
// Masaüstü ortamı (seçilebilir)
|
||||
ui.label(t!("display_manager_de"));
|
||||
let current_de = if state.desktop_environment.is_empty() || state.desktop_environment == "default" {
|
||||
"plasma"
|
||||
let de_current = if state.desktop_environment.is_empty() || state.desktop_environment == "default" {
|
||||
"plasma".to_string()
|
||||
} else {
|
||||
state.desktop_environment.as_str()
|
||||
state.desktop_environment.clone()
|
||||
};
|
||||
egui::ComboBox::from_id_source("de_combo")
|
||||
.selected_text(current_de)
|
||||
.show_ui(ui, |ui| {
|
||||
for de in DE_OPTIONS {
|
||||
ui.selectable_value(
|
||||
&mut state.desktop_environment,
|
||||
de.to_string(),
|
||||
*de,
|
||||
);
|
||||
}
|
||||
});
|
||||
theme::show_combo_box(ui, "de_combo", de_current.as_str(), ui.available_width(), |ui, w| {
|
||||
for de in DE_OPTIONS {
|
||||
theme::selectable_value(ui, &mut state.desktop_environment, de.to_string(), *de, w);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
// Otomatik oturum açma
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
use eframe::egui;
|
||||
use rust_i18n::t;
|
||||
use crate::installer::{GlobalState, InstallerStep};
|
||||
|
||||
pub struct LocationStep {
|
||||
pub regions: Vec<String>,
|
||||
pub zones_by_region: std::collections::HashMap<String, Vec<String>>,
|
||||
pub geoip_done: bool,
|
||||
pub datetime_init_done: bool,
|
||||
}
|
||||
|
||||
fn load_timezones() -> Vec<String> {
|
||||
if let Ok(out) = std::process::Command::new("timedatectl")
|
||||
.args(["list-timezones"])
|
||||
.output()
|
||||
{
|
||||
if out.status.success() {
|
||||
let text = String::from_utf8_lossy(&out.stdout);
|
||||
let zones: Vec<String> = text
|
||||
.lines()
|
||||
.map(|l| l.trim().to_string())
|
||||
.filter(|l| !l.is_empty())
|
||||
.collect();
|
||||
if !zones.is_empty() {
|
||||
return zones;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(text) = std::fs::read_to_string("/usr/share/zoneinfo/zone.tab") {
|
||||
let zones: Vec<String> = text
|
||||
.lines()
|
||||
.filter(|l| !l.starts_with('#') && !l.trim().is_empty())
|
||||
.filter_map(|l| l.split('\t').nth(2))
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
if !zones.is_empty() {
|
||||
return zones;
|
||||
}
|
||||
}
|
||||
vec![
|
||||
"Africa/Cairo", "Africa/Casablanca", "Africa/Johannesburg", "Africa/Lagos",
|
||||
"Africa/Nairobi", "America/Argentina/Buenos_Aires", "America/Bogota",
|
||||
"America/Caracas", "America/Chicago", "America/Denver", "America/Lima",
|
||||
"America/Los_Angeles", "America/Mexico_City", "America/New_York",
|
||||
"America/Panama", "America/Sao_Paulo", "America/Santiago", "America/Toronto",
|
||||
"America/Vancouver", "Asia/Almaty", "Asia/Baghdad", "Asia/Baku",
|
||||
"Asia/Bangkok", "Asia/Dubai", "Asia/Ho_Chi_Minh", "Asia/Hong_Kong",
|
||||
"Asia/Istanbul", "Asia/Jakarta", "Asia/Jerusalem", "Asia/Kabul",
|
||||
"Asia/Karachi", "Asia/Kathmandu", "Asia/Kolkata", "Asia/Kuala_Lumpur",
|
||||
"Asia/Manila", "Asia/Riyadh", "Asia/Seoul", "Asia/Shanghai",
|
||||
"Asia/Singapore", "Asia/Taipei", "Asia/Tashkent", "Asia/Tehran",
|
||||
"Asia/Tokyo", "Asia/Yangon", "Australia/Adelaide", "Australia/Brisbane",
|
||||
"Australia/Melbourne", "Australia/Perth", "Australia/Sydney",
|
||||
"Europe/Amsterdam", "Europe/Athens", "Europe/Berlin", "Europe/Brussels",
|
||||
"Europe/Bucharest", "Europe/Copenhagen", "Europe/Dublin",
|
||||
"Europe/Helsinki", "Europe/Istanbul", "Europe/Kyiv", "Europe/Lisbon",
|
||||
"Europe/London", "Europe/Madrid", "Europe/Moscow", "Europe/Oslo",
|
||||
"Europe/Paris", "Europe/Prague", "Europe/Rome", "Europe/Stockholm",
|
||||
"Europe/Vienna", "Europe/Warsaw", "Europe/Zurich",
|
||||
"Pacific/Auckland", "Pacific/Fiji", "Pacific/Honolulu",
|
||||
"Pacific/Port_Moresby",
|
||||
].iter().map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
||||
fn build_tz_data(zones: &[String]) -> (Vec<String>, std::collections::HashMap<String, Vec<String>>) {
|
||||
let mut regions_set = std::collections::BTreeSet::new();
|
||||
let mut zones_by_region: std::collections::HashMap<String, Vec<String>> = std::collections::HashMap::new();
|
||||
|
||||
for tz in zones {
|
||||
if let Some(slash) = tz.find('/') {
|
||||
let region = tz[..slash].to_string();
|
||||
let zone = tz[slash + 1..].to_string();
|
||||
regions_set.insert(region.clone());
|
||||
zones_by_region.entry(region).or_default().push(zone);
|
||||
}
|
||||
}
|
||||
|
||||
for zones in zones_by_region.values_mut() {
|
||||
zones.sort();
|
||||
}
|
||||
|
||||
let regions: Vec<String> = regions_set.into_iter().collect();
|
||||
(regions, zones_by_region)
|
||||
}
|
||||
|
||||
impl Default for LocationStep {
|
||||
fn default() -> Self {
|
||||
let all_zones = load_timezones();
|
||||
let (regions, zones_by_region) = build_tz_data(&all_zones);
|
||||
Self {
|
||||
regions,
|
||||
zones_by_region,
|
||||
geoip_done: false,
|
||||
datetime_init_done: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LocationStep {
|
||||
fn geoip_lookup(state: &mut GlobalState) {
|
||||
let out = std::process::Command::new("curl")
|
||||
.args(["-s", "--connect-timeout", "3", "http://ip-api.com/json"])
|
||||
.output();
|
||||
if let Ok(o) = out {
|
||||
if let Ok(text) = String::from_utf8(o.stdout) {
|
||||
let extract = |key: &str| -> Option<String> {
|
||||
let pat = format!("\"{}\":\"", key);
|
||||
text.find(&pat).and_then(|pos| {
|
||||
let start = pos + pat.len();
|
||||
text[start..].find('"').map(|end| text[start..start+end].to_string())
|
||||
})
|
||||
};
|
||||
if let Some(country) = extract("countryCode") {
|
||||
match country.as_str() {
|
||||
"TR" => state.language = "tr".to_string(),
|
||||
"DE" => state.language = "de".to_string(),
|
||||
"FR" => state.language = "fr".to_string(),
|
||||
"IT" => state.language = "it".to_string(),
|
||||
"ES" => state.language = "es".to_string(),
|
||||
"RU" => state.language = "ru".to_string(),
|
||||
"NL" => state.language = "nl".to_string(),
|
||||
"PL" => state.language = "pl".to_string(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(tz) = extract("timezone") {
|
||||
if !tz.is_empty() {
|
||||
state.timezone = tz;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_from_system(&mut self, state: &mut GlobalState) {
|
||||
use chrono::{Datelike, Timelike};
|
||||
let now_str = chrono::Local::now().format("%Y-%m-%d %H:%M").to_string();
|
||||
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(&now_str, "%Y-%m-%d %H:%M") {
|
||||
state.manual_year = dt.year();
|
||||
state.manual_month = dt.month();
|
||||
state.manual_day = dt.day();
|
||||
state.manual_hour = dt.hour();
|
||||
state.manual_minute = dt.minute();
|
||||
}
|
||||
self.datetime_init_done = true;
|
||||
}
|
||||
|
||||
fn split_tz(tz: &str) -> (String, String) {
|
||||
if let Some(slash) = tz.find('/') {
|
||||
(tz[..slash].to_string(), tz[slash + 1..].to_string())
|
||||
} else {
|
||||
("Other".to_string(), tz.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InstallerStep for LocationStep {
|
||||
fn name(&self) -> String {
|
||||
format!("{} / {}", t!("location"), t!("datetime"))
|
||||
}
|
||||
|
||||
fn on_enter(&mut self, state: &mut GlobalState) {
|
||||
if !self.geoip_done {
|
||||
Self::geoip_lookup(state);
|
||||
self.geoip_done = true;
|
||||
}
|
||||
if !self.datetime_init_done {
|
||||
self.init_from_system(state);
|
||||
}
|
||||
if state.timezone.is_empty() {
|
||||
state.timezone = "Europe/Istanbul".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) {
|
||||
use crate::ui::theme;
|
||||
|
||||
theme::section_heading(ui, &t!("location_title"));
|
||||
ui.label(
|
||||
egui::RichText::new(t!("location_description"))
|
||||
.color(theme::c_text_dim())
|
||||
.size(13.0),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
let (mut cur_region, mut cur_zone) = Self::split_tz(&state.timezone);
|
||||
|
||||
if !self.regions.contains(&cur_region) {
|
||||
if let Some(first) = self.regions.first() {
|
||||
cur_region = first.clone();
|
||||
if let Some(zones) = self.zones_by_region.get(&cur_region) {
|
||||
cur_zone = zones.first().cloned().unwrap_or_default();
|
||||
}
|
||||
state.timezone = format!("{}/{}", cur_region, cur_zone);
|
||||
}
|
||||
}
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.label(egui::RichText::new(t!("region")).strong().color(theme::c_text()));
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
|
||||
let rw = 140.0_f32.min(ui.available_width() / 3.0);
|
||||
let region_text = cur_region.clone();
|
||||
theme::show_combo_box(ui, "tz_region_select", region_text.as_str(), rw, |ui, w| {
|
||||
for region in &self.regions {
|
||||
if theme::selectable_value(ui, &mut cur_region, region.clone(), region.as_str(), w).clicked() {
|
||||
if let Some(zones) = self.zones_by_region.get(&cur_region) {
|
||||
cur_zone = zones.first().cloned().unwrap_or_default();
|
||||
}
|
||||
state.timezone = format!("{}/{}", cur_region, cur_zone);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(16.0);
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.label(egui::RichText::new(t!("timezone")).strong().color(theme::c_text()));
|
||||
});
|
||||
ui.add_space(4.0);
|
||||
|
||||
let zw = ui.available_width().max(160.0);
|
||||
if let Some(zones) = self.zones_by_region.get(&cur_region) {
|
||||
if !zones.contains(&cur_zone) {
|
||||
cur_zone = zones.first().cloned().unwrap_or_default();
|
||||
state.timezone = format!("{}/{}", cur_region, cur_zone);
|
||||
}
|
||||
let zone_text = cur_zone.clone();
|
||||
theme::show_combo_box(ui, "tz_zone_select", zone_text.as_str(), zw, |ui, w| {
|
||||
for zone in zones {
|
||||
if theme::selectable_value(ui, &mut cur_zone, zone.clone(), zone.as_str(), w).clicked() {
|
||||
state.timezone = format!("{}/{}", cur_region, cur_zone);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(16.0);
|
||||
ui.separator();
|
||||
ui.add_space(16.0);
|
||||
|
||||
theme::section_heading(ui, &t!("datetime_title"));
|
||||
ui.label(
|
||||
egui::RichText::new(t!("datetime_description"))
|
||||
.color(theme::c_text_dim())
|
||||
.size(13.0),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
egui::Frame::group(ui.style())
|
||||
.rounding(6.0)
|
||||
.inner_margin(egui::Margin::same(12.0))
|
||||
.show(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
egui::Grid::new("datetime_grid")
|
||||
.num_columns(2)
|
||||
.spacing([12.0, 10.0])
|
||||
.show(ui, |ui| {
|
||||
ui.label(egui::RichText::new(t!("datetime_ntp_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::Checkbox::new(&mut state.use_ntp, ""));
|
||||
let status = if state.use_ntp { t!("datetime_ntp_on") } else { t!("datetime_ntp_off") };
|
||||
ui.colored_label(
|
||||
if state.use_ntp { theme::c_success() } else { theme::c_error() },
|
||||
status,
|
||||
);
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
if !state.use_ntp {
|
||||
ui.label(egui::RichText::new(t!("datetime_date_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(t!("datetime_year"));
|
||||
ui.add(egui::DragValue::new(&mut state.manual_year).clamp_range(2000..=2100).speed(1));
|
||||
ui.label(t!("datetime_month"));
|
||||
ui.add(egui::DragValue::new(&mut state.manual_month).clamp_range(1..=12).speed(1));
|
||||
ui.label(t!("datetime_day"));
|
||||
ui.add(egui::DragValue::new(&mut state.manual_day).clamp_range(1..=31).speed(1));
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label(egui::RichText::new(t!("datetime_time_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(egui::DragValue::new(&mut state.manual_hour).clamp_range(0..=23).speed(1));
|
||||
ui.label(":");
|
||||
ui.add(egui::DragValue::new(&mut state.manual_minute).clamp_range(0..=59).speed(1));
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
if state.use_ntp {
|
||||
ui.label(
|
||||
egui::RichText::new(t!("datetime_ntp_info"))
|
||||
.size(11.0)
|
||||
.color(theme::c_text_dim()),
|
||||
);
|
||||
} else {
|
||||
ui.label(
|
||||
egui::RichText::new(t!("datetime_manual_info"))
|
||||
.size(11.0)
|
||||
.color(theme::c_text_dim()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete(&self, state: &GlobalState) -> bool {
|
||||
!state.timezone.is_empty()
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ pub mod network;
|
||||
pub mod rescue;
|
||||
pub mod display_manager;
|
||||
pub mod netinstall;
|
||||
pub mod location;
|
||||
|
||||
pub use keyboard::KeyboardStep;
|
||||
pub use users::UsersStep;
|
||||
@@ -20,3 +21,4 @@ pub use bootloader::BootloaderStep;
|
||||
pub use network::NetworkStep;
|
||||
pub use rescue::RescueStep;
|
||||
pub use display_manager::DisplayManagerStep;
|
||||
pub use location::LocationStep;
|
||||
|
||||
+28
-47
@@ -2,6 +2,7 @@
|
||||
|
||||
use eframe::egui;
|
||||
use rust_i18n::t;
|
||||
use crate::ui::theme;
|
||||
// sysinfo::Disks mount point başına bir kayıt döndürür (partition'lar),
|
||||
// ham disk listesi için lsblk kullanıyoruz.
|
||||
|
||||
@@ -892,15 +893,12 @@ impl PartitionStep {
|
||||
// Dosya sistemi (boyut önerisi ile)
|
||||
ui.label(t!("mp_fstype"));
|
||||
let prev_fs = self.manual.add_fstype;
|
||||
egui::ComboBox::from_id_source("fs_combo")
|
||||
.selected_text(FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.0).unwrap_or("?"))
|
||||
.show_ui(ui, |ui| {
|
||||
for (i, (label, _)) in FS_OPTIONS.iter().enumerate() {
|
||||
ui.selectable_value(
|
||||
&mut self.manual.add_fstype, i, *label
|
||||
);
|
||||
}
|
||||
});
|
||||
let fs_label = FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.0).unwrap_or("?");
|
||||
theme::show_combo_box(ui, "fs_combo", fs_label, ui.available_width(), |ui, w| {
|
||||
for (i, (label, _)) in FS_OPTIONS.iter().enumerate() {
|
||||
theme::selectable_value(ui, &mut self.manual.add_fstype, i, *label, w);
|
||||
}
|
||||
});
|
||||
// FS tipi değişince akıllı boyut öner
|
||||
if prev_fs != self.manual.add_fstype && self.manual.add_size_str.is_empty() {
|
||||
let fs = FS_OPTIONS.get(self.manual.add_fstype).map(|x| &x.1);
|
||||
@@ -925,16 +923,11 @@ impl PartitionStep {
|
||||
} else {
|
||||
self.manual.add_mountpoint.clone()
|
||||
};
|
||||
egui::ComboBox::from_id_source("mp_combo")
|
||||
.selected_text(¤t_mp)
|
||||
.width(120.0)
|
||||
.show_ui(ui, |ui| {
|
||||
for mp in COMMON_MOUNTPOINTS {
|
||||
if ui.selectable_label(false, *mp).clicked() {
|
||||
self.manual.add_mountpoint = mp.to_string();
|
||||
}
|
||||
}
|
||||
});
|
||||
theme::show_combo_box(ui, "mp_combo", ¤t_mp, 120.0, |ui, w| {
|
||||
for mp in COMMON_MOUNTPOINTS {
|
||||
theme::selectable_value(ui, &mut self.manual.add_mountpoint, mp.to_string(), *mp, w);
|
||||
}
|
||||
});
|
||||
let mp_field = egui::TextEdit::singleline(&mut self.manual.add_mountpoint)
|
||||
.hint_text(t!("mp_add_dialog_hint"));
|
||||
ui.add(mp_field);
|
||||
@@ -1143,15 +1136,11 @@ impl PartitionStep {
|
||||
let current_vg = state.volume_groups.get(self.manual.add_lv_vg_idx)
|
||||
.map(|vg| vg.name.as_str())
|
||||
.unwrap_or("?");
|
||||
egui::ComboBox::from_id_source("lv_vg_combo")
|
||||
.selected_text(current_vg)
|
||||
.show_ui(ui, |ui| {
|
||||
for (vi, vg) in state.volume_groups.iter().enumerate() {
|
||||
ui.selectable_value(
|
||||
&mut self.manual.add_lv_vg_idx, vi, &vg.name
|
||||
);
|
||||
}
|
||||
});
|
||||
theme::show_combo_box(ui, "lv_vg_combo", current_vg, ui.available_width(), |ui, w| {
|
||||
for (vi, vg) in state.volume_groups.iter().enumerate() {
|
||||
theme::selectable_value(ui, &mut self.manual.add_lv_vg_idx, vi, vg.name.as_str(), w);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
// LV Adı
|
||||
@@ -1161,15 +1150,12 @@ impl PartitionStep {
|
||||
|
||||
// Dosya sistemi
|
||||
ui.label(t!("mp_fstype"));
|
||||
egui::ComboBox::from_id_source("lv_fs_combo")
|
||||
.selected_text(FS_OPTIONS.get(self.manual.add_lv_fstype).map(|x| x.0).unwrap_or("?"))
|
||||
.show_ui(ui, |ui| {
|
||||
for (i, (label, _)) in FS_OPTIONS.iter().enumerate() {
|
||||
ui.selectable_value(
|
||||
&mut self.manual.add_lv_fstype, i, *label
|
||||
);
|
||||
}
|
||||
});
|
||||
let lv_fs_label = FS_OPTIONS.get(self.manual.add_lv_fstype).map(|x| x.0).unwrap_or("?");
|
||||
theme::show_combo_box(ui, "lv_fs_combo", lv_fs_label, ui.available_width(), |ui, w| {
|
||||
for (i, (label, _)) in FS_OPTIONS.iter().enumerate() {
|
||||
theme::selectable_value(ui, &mut self.manual.add_lv_fstype, i, *label, w);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
// Boyut
|
||||
@@ -1185,16 +1171,11 @@ impl PartitionStep {
|
||||
} else {
|
||||
self.manual.add_lv_mountpoint.clone()
|
||||
};
|
||||
egui::ComboBox::from_id_source("lv_mp_combo")
|
||||
.selected_text(¤t_mp)
|
||||
.width(120.0)
|
||||
.show_ui(ui, |ui| {
|
||||
for mp in COMMON_MOUNTPOINTS {
|
||||
if ui.selectable_label(false, *mp).clicked() {
|
||||
self.manual.add_lv_mountpoint = mp.to_string();
|
||||
}
|
||||
}
|
||||
});
|
||||
theme::show_combo_box(ui, "lv_mp_combo", ¤t_mp, 120.0, |ui, w| {
|
||||
for mp in COMMON_MOUNTPOINTS {
|
||||
theme::selectable_value(ui, &mut self.manual.add_lv_mountpoint, mp.to_string(), *mp, w);
|
||||
}
|
||||
});
|
||||
let mp_field = egui::TextEdit::singleline(&mut self.manual.add_lv_mountpoint)
|
||||
.hint_text(t!("mp_add_dialog_hint"));
|
||||
ui.add(mp_field);
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
use eframe::egui::{self, Rect, Vec2};
|
||||
use super::combobox_stil::{draw_gradient_bg, DROPDOWN_TEXT_COLOR};
|
||||
|
||||
/// Stil uygulanmış ComboBox oluşturur (genişlik ayarı yapılmaz, Grid içinde kullanım için).
|
||||
pub fn combo_box(
|
||||
id_source: impl std::hash::Hash,
|
||||
selected_text: impl Into<egui::WidgetText>,
|
||||
) -> egui::ComboBox {
|
||||
egui::ComboBox::from_id_source(id_source).selected_text(selected_text)
|
||||
}
|
||||
|
||||
/// Stil uygulanmış ComboBox oluşturur ve genişlik verir.
|
||||
pub fn combo_box_with_width(
|
||||
id_source: impl std::hash::Hash,
|
||||
selected_text: impl Into<egui::WidgetText>,
|
||||
width: f32,
|
||||
) -> egui::ComboBox {
|
||||
combo_box(id_source, selected_text).width(width)
|
||||
}
|
||||
|
||||
/// ComboBox dropdown'ını açar, genişlik kısıtlaması uygular ve içeriği çizer.
|
||||
pub fn show_combo_box<R>(
|
||||
ui: &mut egui::Ui,
|
||||
id_source: impl std::hash::Hash,
|
||||
selected_text: impl Into<egui::WidgetText>,
|
||||
width: f32,
|
||||
add_contents: impl FnOnce(&mut egui::Ui, f32) -> R,
|
||||
) -> Option<R> {
|
||||
combo_box_with_width(id_source, selected_text, width)
|
||||
.show_ui(ui, |ui| {
|
||||
ui.set_min_width(width);
|
||||
ui.set_max_width(width);
|
||||
add_contents(ui, width)
|
||||
})
|
||||
.inner
|
||||
}
|
||||
|
||||
/// ComboBox dropdown öğesi — gradient arkaplan, tam genişlik tıklanabilir, pointing hand imleci.
|
||||
pub fn selectable_value<T: PartialEq>(
|
||||
ui: &mut egui::Ui,
|
||||
current: &mut T,
|
||||
value: T,
|
||||
label: &str,
|
||||
width: f32,
|
||||
) -> egui::Response {
|
||||
let item_height = 28.0;
|
||||
let (rect, response) = ui.allocate_exact_size(Vec2::new(width, item_height), egui::Sense::click());
|
||||
|
||||
draw_gradient_bg(ui, rect);
|
||||
|
||||
ui.put(rect, |ui: &mut egui::Ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(6.0);
|
||||
ui.colored_label(DROPDOWN_TEXT_COLOR, label);
|
||||
})
|
||||
.response
|
||||
});
|
||||
|
||||
if response.clicked() {
|
||||
*current = value;
|
||||
}
|
||||
response.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
}
|
||||
|
||||
/// ComboBox dropdown öğesi — özel içerik çizmek için (ör. bayrak + metin).
|
||||
/// `add_contents` callback'i verilen `Rect` içine içerik ekler.
|
||||
pub fn selectable_value_custom<R>(
|
||||
ui: &mut egui::Ui,
|
||||
width: f32,
|
||||
add_contents: impl FnOnce(&mut egui::Ui, Rect) -> R,
|
||||
) -> egui::Response {
|
||||
let item_height = 28.0;
|
||||
let (rect, mut response) = ui.allocate_exact_size(Vec2::new(width, item_height), egui::Sense::click());
|
||||
|
||||
draw_gradient_bg(ui, rect);
|
||||
|
||||
add_contents(ui, rect);
|
||||
|
||||
if response.clicked() {
|
||||
response.mark_changed();
|
||||
}
|
||||
response.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
use eframe::egui::{self, Color32, Rect, Stroke};
|
||||
use egui::epaint::Mesh;
|
||||
|
||||
// ─── ComboBox Popup Renk Sabitleri ──────────────────────────
|
||||
pub const WEAK_BG_INACTIVE: Color32 = Color32::from_rgb(224, 224, 224);
|
||||
pub const WEAK_BG_HOVERED: Color32 = Color32::from_rgb(200, 200, 200);
|
||||
pub const WEAK_BG_ACTIVE: Color32 = Color32::from_rgb(180, 180, 180);
|
||||
|
||||
// ─── Gradient Dropdown Öğesi Renkleri ───────────────────────
|
||||
pub const GRADIENT_TOP: Color32 = Color32::from_rgb(249, 249, 249);
|
||||
pub const GRADIENT_BOT: Color32 = Color32::from_rgb(233, 233, 233);
|
||||
|
||||
// ─── ComboBox Metin Renkleri ────────────────────────────────
|
||||
/// Seçili metin rengi (ComboBox kapalıyken widget üzerinde görünen metin)
|
||||
pub const COMBO_TEXT_COLOR: Color32 = Color32::from_rgb(50, 50, 50);
|
||||
/// Açılır listedeki öğelerin metin rengi
|
||||
pub const DROPDOWN_TEXT_COLOR: Color32 = Color32::from_rgb(50, 50, 50);
|
||||
|
||||
// ─── ComboBox Kenarlık (border) Renkleri ────────────────────
|
||||
/// Üzerine gelindiğinde / tıklanınca kenarlık rengi
|
||||
pub const COMBO_BORDER_ACCENT: Color32 = Color32::from_rgb(155, 155, 155);
|
||||
|
||||
/// Verilen `Visuals` yapısına ComboBox stillerini uygular.
|
||||
pub fn apply_visuals(v: &mut egui::Visuals) {
|
||||
v.widgets.inactive.weak_bg_fill = WEAK_BG_INACTIVE;
|
||||
v.widgets.hovered.weak_bg_fill = WEAK_BG_HOVERED;
|
||||
v.widgets.active.weak_bg_fill = WEAK_BG_ACTIVE;
|
||||
|
||||
v.widgets.inactive.fg_stroke = Stroke::new(1.0, COMBO_TEXT_COLOR);
|
||||
v.widgets.hovered.fg_stroke = Stroke::new(1.5, Color32::WHITE);
|
||||
v.widgets.active.fg_stroke = Stroke::new(2.0, Color32::WHITE);
|
||||
|
||||
// Kenarlıklar
|
||||
v.widgets.inactive.bg_stroke = Stroke::new(1.0, COMBO_BORDER_ACCENT);
|
||||
v.widgets.hovered.bg_stroke = Stroke::new(1.0, COMBO_BORDER_ACCENT);
|
||||
v.widgets.active.bg_stroke = Stroke::new(1.0, COMBO_BORDER_ACCENT);
|
||||
}
|
||||
|
||||
/// Açık gri gradyan mesh arkaplanı çizer (dropdown öğeleri için).
|
||||
pub fn draw_gradient_bg(ui: &mut egui::Ui, rect: Rect) {
|
||||
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: GRADIENT_TOP,
|
||||
});
|
||||
mesh.vertices.push(egui::epaint::Vertex {
|
||||
pos: egui::pos2(rect.right(), rect.top()),
|
||||
uv: egui::pos2(0.0, 0.0),
|
||||
color: GRADIENT_TOP,
|
||||
});
|
||||
mesh.vertices.push(egui::epaint::Vertex {
|
||||
pos: egui::pos2(rect.right(), rect.bottom()),
|
||||
uv: egui::pos2(0.0, 0.0),
|
||||
color: GRADIENT_BOT,
|
||||
});
|
||||
mesh.vertices.push(egui::epaint::Vertex {
|
||||
pos: egui::pos2(rect.left(), rect.bottom()),
|
||||
uv: egui::pos2(0.0, 0.0),
|
||||
color: GRADIENT_BOT,
|
||||
});
|
||||
mesh.indices.extend_from_slice(&[idx, idx + 1, idx + 2, idx + 2, idx + 3, idx]);
|
||||
ui.painter().add(egui::Shape::mesh(mesh));
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod theme;
|
||||
pub mod buttons;
|
||||
pub mod combobox;
|
||||
pub mod combobox_stil;
|
||||
pub mod slideshow;
|
||||
pub mod error_screen;
|
||||
|
||||
|
||||
+15
-15
@@ -63,6 +63,7 @@ pub fn set_theme_mode(is_dark: bool) {
|
||||
TEXT.store(0x000000, Ordering::Relaxed);
|
||||
TEXT_DIM.store(0x000000, Ordering::Relaxed);
|
||||
SIDEBAR_TEXT.store(0xffffff, Ordering::Relaxed);
|
||||
|
||||
|
||||
// BG_DARK.store(0xF0F0F5, Ordering::Relaxed);
|
||||
// BG_PANEL.store(0xFFFFFF, Ordering::Relaxed);
|
||||
@@ -92,8 +93,8 @@ pub fn c_accent_dim() -> Color32 {
|
||||
/// `cc.egui_ctx` üzerinden `setup_custom_fonts` ve `set_visuals` çağrısını birleştirir.
|
||||
pub fn apply(ctx: &egui::Context, is_dark: bool) {
|
||||
setup_fonts(ctx);
|
||||
ctx.set_visuals(build_visuals(is_dark));
|
||||
ctx.set_style(build_style());
|
||||
ctx.set_visuals(build_visuals(is_dark));
|
||||
}
|
||||
|
||||
fn setup_fonts(ctx: &egui::Context) {
|
||||
@@ -225,27 +226,25 @@ fn build_visuals(is_dark: bool) -> Visuals {
|
||||
|
||||
// ─── KENARLIKLAR (Calamares Uyumu için İnce İnce Yumuşatıldı) ───────────────────
|
||||
v.widgets.noninteractive.bg_stroke = Stroke::new(1.0, c_border());
|
||||
v.widgets.inactive.bg_stroke = Stroke::new(1.0, Color32::from_rgba_unmultiplied(255, 255, 255, 30)); // Buton etrafında hafif saydam parlaklık
|
||||
v.widgets.hovered.bg_stroke = Stroke::new(1.5, c_accent());
|
||||
v.widgets.active.bg_stroke = Stroke::new(2.0, c_accent());
|
||||
|
||||
// ─── WIDGET DOLGULARI (Burası ComboBox ve Butonların Kaderini Belirliyor) ───────
|
||||
v.widgets.noninteractive.bg_fill = c_bg_widget();
|
||||
|
||||
// Pasif Hali: Calamares Mavisi (İstiyorsan c_accent() ile Pisi Vişne Rengine de Çekebilirsin)
|
||||
v.widgets.inactive.bg_fill = Color32::from_rgb(46, 124, 219);
|
||||
// Pasif Hali: Koyu widget arka planı
|
||||
//v.widgets.inactive.bg_fill = Color32::from_rgb(40, 43, 52);
|
||||
//v.widgets.inactive.bg_fill = Color32::from_rgb(224, 224, 224);
|
||||
|
||||
// Üzerine Gelme Hali: Parlayan Açık Mavi
|
||||
v.widgets.hovered.bg_fill = Color32::from_rgb(59, 144, 240);
|
||||
// Üzerine Gelme Hali: Hafif açık
|
||||
v.widgets.hovered.bg_fill = Color32::from_rgb(55, 58, 68);
|
||||
|
||||
// Tıklanma/Açık Kalma Hali: Koyu Lacivert/Mavi
|
||||
v.widgets.active.bg_fill = Color32::from_rgb(20, 70, 140);
|
||||
// Tıklanma/Açık Kalma Hali: Daha açık
|
||||
v.widgets.active.bg_fill = Color32::from_rgb(60, 63, 75);
|
||||
|
||||
// ComboBox stilleri (combobox_stil.rs)
|
||||
crate::ui::combobox_stil::apply_visuals(&mut v);
|
||||
|
||||
// ─── METİN RENKLERİ ────────────────────────────────────────────────────────────
|
||||
v.widgets.noninteractive.fg_stroke = Stroke::new(1.0, c_text());
|
||||
v.widgets.inactive.fg_stroke = Stroke::new(1.0, Color32::WHITE); // Mavi üstüne beyaz metin okunaklı kılar
|
||||
v.widgets.hovered.fg_stroke = Stroke::new(1.5, Color32::WHITE);
|
||||
v.widgets.active.fg_stroke = Stroke::new(2.0, Color32::WHITE);
|
||||
|
||||
// ─── YUVARLAK KÖŞELER (Zaten 6.0'dı, Tam İstediğin Calamares Kıvamında) ─────────
|
||||
v.widgets.noninteractive.rounding = Rounding::same(6.0);
|
||||
@@ -264,6 +263,8 @@ fn build_visuals(is_dark: bool) -> Visuals {
|
||||
v.window_stroke = Stroke::new(1.0, c_border());
|
||||
v.window_rounding = Rounding::same(8.0);
|
||||
|
||||
v.interact_cursor = Some(egui::CursorIcon::PointingHand);
|
||||
|
||||
v
|
||||
}
|
||||
|
||||
@@ -286,14 +287,13 @@ fn build_style() -> egui::Style {
|
||||
]
|
||||
.into();
|
||||
|
||||
style.visuals.interact_cursor = Some(egui::CursorIcon::PointingHand);
|
||||
|
||||
style
|
||||
}
|
||||
|
||||
// ─── Yardımcı widget'lar ─────────────────────────────────────────
|
||||
|
||||
pub use super::buttons::*;
|
||||
pub use super::combobox::*;
|
||||
|
||||
/// Bölüm başlığı — büyük, açık renkli, alt çizgisiz.
|
||||
pub fn section_heading(ui: &mut egui::Ui, text: &str) {
|
||||
|
||||
Reference in New Issue
Block a user