Refactor layout code

- Cleanup variable names
- Refactor row & column code
This commit is contained in:
Candifloss 2025-11-10 22:27:34 +05:30
parent f56b543a0b
commit 4d10bffe8b
4 changed files with 122 additions and 368 deletions

View File

@ -1,3 +0,0 @@
fn main() {
slint_build::compile("ui/widget-popup.slint").unwrap();
}

View File

@ -1,27 +1,26 @@
use iced::{ use iced::{
Alignment, Background, Border, Color, Font, Gradient, Length, Point, Settings, Shadow, Size, Alignment, Background, Border, Color, Font, Length, Point, Settings, Shadow, Size, Task,
Task, Vector, Vector,
alignment::{Horizontal, Vertical}, alignment::{Horizontal, Vertical},
application::Appearance, application::Appearance,
border, border,
font::Family, font::Family,
gradient, widget::{Column, Container, Row, Text, container},
widget::{Column, Container, Row, Space, Text, column, container, container::Style, row},
window, window,
}; };
use owm_rs::free_api_v25::current::WeatherResponse; use owm_rs::free_api_v25::current::WeatherResponse;
use owm_widg_config; use owm_widg_config::config::Config;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Message {} enum Message {}
struct WeatherPopup { struct WeatherPopup {
resp: WeatherResponse, resp: WeatherResponse,
conf: owm_widg_config::config::Config, conf: Config,
} }
impl WeatherPopup { impl WeatherPopup {
fn new(resp: WeatherResponse, conf: owm_widg_config::config::Config) -> Self { fn new(resp: WeatherResponse, conf: Config) -> Self {
Self { resp, conf } Self { resp, conf }
} }
@ -30,20 +29,20 @@ impl WeatherPopup {
} }
fn view(&self) -> iced::Element<Message> { fn view(&self) -> iced::Element<Message> {
// Data // Extract data
let city = self.resp.name.clone().unwrap_or_else(|| "Unknown".into()); let city = self.resp.name.clone().unwrap_or_else(|| "Unknown".into());
let country = self let country = self
.resp .resp
.sys .sys
.as_ref() .as_ref()
.and_then(|s| s.country.clone()) .and_then(|s| s.country.clone())
.unwrap_or_else(|| String::new()); .unwrap_or_default();
let (weather_main, weather_description, icon_code) = let (main, desc, icon) = self
if let Some(w) = self.resp.weather.first() { .resp
(w.main.clone(), w.description.clone(), w.icon.clone()) .weather
} else { .first()
("N/A".into(), "N/A".into(), String::new()) .map(|w| (w.main.clone(), w.description.clone(), w.icon.clone()))
}; .unwrap_or(("N/A".into(), "N/A".into(), String::new()));
let temp = self.resp.main.as_ref().and_then(|m| m.temp).unwrap_or(0.0); let temp = self.resp.main.as_ref().and_then(|m| m.temp).unwrap_or(0.0);
let temperature = format!("{temp:.1}"); let temperature = format!("{temp:.1}");
let unit = match self.conf.query_params.units.as_str() { let unit = match self.conf.query_params.units.as_str() {
@ -53,204 +52,145 @@ impl WeatherPopup {
_ => '?', _ => '?',
}; };
// UI let font = Font {
let default_font = "IosevkaTermSlab Nerd Font Mono"; family: Family::Name("IosevkaTermSlab Nerd Font Mono"),
//row1col1 ..Font::DEFAULT
let icon_text: Text = Text::new(icon_to_nerd_font(&icon_code)) };
.font(Font {
family: Family::Name(default_font), // Top layout
..Font::DEFAULT let icon_text = Text::new(icon_to_nerd_font(&icon))
}) .font(font)
.align_x(Horizontal::Left) .size(80)
.size(60) .height(Length::Fill)
.color(Color::from_rgb(1.0, 1.0, 1.0))
.into();
let icon_column = Column::new().push(icon_text).width(Length::FillPortion(1));
// row1col2row1
let location_text: Text = Text::new(format!("{city}, {country}"))
.font(Font {
family: Family::Name(default_font),
..Font::DEFAULT
})
.size(16)
.color(Color::from_rgb(1.0, 1.0, 1.0))
.width(Length::Fill) .width(Length::Fill)
.align_x(Horizontal::Right) .align_x(Horizontal::Center)
.into(); .align_y(Vertical::Center)
let location_row = Column::new() .color(Color::WHITE);
.push(location_text) let icon_block = Column::new()
.push(icon_text)
.height(Length::Fill)
.width(Length::FillPortion(1)); .width(Length::FillPortion(1));
// row1col2row2col1 let location_text = Text::new(format!("{city}, {country}"))
let temp_val_text: Text = Text::new(format!("{temperature}")) .font(font)
.font(Font { .size(16)
family: Family::Name(default_font), .color(Color::WHITE)
..Font::DEFAULT .align_x(Horizontal::Right)
}) .width(Length::Fill)
.height(Length::FillPortion(1));
let temp_text = Text::new(temperature)
.font(font)
.size(40) .size(40)
.color(Color::from_rgb(1.0, 1.0, 1.0)) .align_x(Horizontal::Right)
.into(); .align_y(Vertical::Center)
let temp_val_col = Column::new() .height(Length::Fill)
.push(temp_val_text) .width(Length::Fill)
.height(40) .color(Color::WHITE);
.width(Length::Fill); let temp_val_column = Column::new()
.push(temp_text)
// row1col2row2col2row1
let degree_symbol: Text = Text::new(format!("°"))
.font(Font {
family: Family::Name(default_font),
..Font::DEFAULT
})
.size(16)
.color(Color::from_rgb(1.0, 1.0, 1.0))
.into();
let degree_symbol_row: Row<Message> =
Row::new().push(degree_symbol).height(20).width(10).into();
// row1col2row2col2row2
let unit_symbol: Text = Text::new(format!("{unit}"))
.font(Font {
family: Family::Name(default_font),
..Font::DEFAULT
})
.size(16)
.color(Color::from_rgb(1.0, 1.0, 1.0))
.into();
let unit_symbol_row: Row<Message> =
Row::new().push(unit_symbol).height(20).width(10).into();
// row1col2row2col2
let temp_unit_col: Column<Message> = Column::new()
.push(degree_symbol_row)
.push(unit_symbol_row)
.height(40)
.width(10)
.into();
// row1col2row2
let temp_row: Row<Message> = Row::new()
.push(temp_val_col)
.push(temp_unit_col)
.height(Length::FillPortion(3)) .height(Length::FillPortion(3))
.width(Length::Fill); .width(Length::Fill);
let degree_text = Text::new("O").font(font).size(16).color(Color::WHITE);
let row1col2: Column<Message> = Column::new() let unit_text = Text::new(format!("{unit}"))
.push(location_row) .font(font)
.push(temp_row)
.height(Length::Fill)
.width(Length::Fill)
.into();
// row2col1
let weather_main_text: Text = Text::new(format!("{weather_main}"))
.font(Font {
family: Family::Name(default_font),
..Font::DEFAULT
})
.size(16) .size(16)
.color(Color::from_rgb(1.0, 1.0, 1.0)) .color(Color::WHITE);
.width(Length::Fill) let unit_block = Column::with_children(vec![
.into(); Row::new().push(degree_text).height(Length::Fill).into(),
let weather_main_col: Column<Message> = Column::new() Row::new().push(unit_text).height(Length::Fill).into(),
.push(weather_main_text) ]);
.height(Length::Fill)
.width(Length::FillPortion(3))
.into();
// row2col1 let temp_block = Row::new()
let weather_descr_text: Text = Text::new(format!("{weather_description}")) .push(temp_val_column)
.font(Font { .push(Column::new().push(unit_block).height(Length::Fill))
family: Family::Name(default_font), .width(Length::Fill);
..Font::DEFAULT
}) let right_block = Column::new()
.push(location_text)
.push(temp_block)
.width(Length::Fill)
.height(Length::Fill);
let top_row = Row::new()
.push(icon_block)
.push(right_block)
.width(Length::Fill)
.height(Length::FillPortion(8));
// Bottom layout
let main_text = Text::new(main)
.font(font)
.size(16) .size(16)
.color(Color::from_rgb(1.0, 1.0, 1.0)) .color(Color::WHITE)
.width(Length::Fill) .width(Length::FillPortion(3));
.into();
let weather_descr_col: Column<Message> = Column::new()
.push(weather_descr_text)
.height(Length::Fill)
.width(Length::FillPortion(4))
.into();
let row2: Row<Message> = Row::new() let desc_text = Text::new(desc)
.push(weather_main_col) .font(font)
.push(weather_descr_col) .size(16)
.height(Length::FillPortion(3)) .color(Color::from_rgba(1.0, 1.0, 1.0, 0.8))
.width(Length::Fill) .width(Length::FillPortion(4));
.into();
let row1: Row<Message> = Row::new() let bottom_row = Row::new()
.push(icon_column) .push(main_text)
.push(row1col2) .push(desc_text)
.height(Length::FillPortion(8))
.width(Length::Fill) .width(Length::Fill)
.into(); .height(Length::FillPortion(3));
let whole_thing: Column<Message> = Column::new() // Combine
.push(row1) let content = Column::new()
.push(row2) .push(top_row)
.height(Length::Fill) .push(bottom_row)
.spacing(6)
.width(Length::Fill) .width(Length::Fill)
.into(); .height(Length::Fill);
container(whole_thing) container(content)
.style(|_theme| Style { .style(|_theme| iced::widget::container::Style {
background: Some(Background::from(Color::from_rgba(0.20, 0.43, 0.55, 0.25))), background: Some(Background::from(Color::from_rgba(0.20, 0.43, 0.55, 0.25))),
text_color: Some(Color::from_rgba(1.0, 1.0, 1.0, 0.25)),
border: Border { border: Border {
color: Color::TRANSPARENT, color: Color::TRANSPARENT,
width: 0.0, width: 0.0,
radius: border::Radius::from(10.0), radius: border::Radius::from(10.0),
}, },
shadow: Shadow { shadow: Shadow::default(),
color: Color::TRANSPARENT, text_color: Some(Color::WHITE),
offset: Vector { x: 0.0, y: 0.0 },
blur_radius: 0.0,
},
}) })
.width(Length::Fill)
.height(Length::Fill)
.padding(10) .padding(10)
.into() .into()
} }
} }
pub fn show_popup(resp: WeatherResponse, conf: owm_widg_config::config::Config) -> iced::Result { pub fn show_popup(resp: WeatherResponse, conf: Config) -> iced::Result {
iced::application( iced::application("Weather Popup", WeatherPopup::update, WeatherPopup::view)
"Weather Popup", // Title .window(window::Settings {
WeatherPopup::update, size: Size {
WeatherPopup::view, width: 300.0,
) height: 150.0,
.window(window::Settings { },
size: Size { position: window::Position::Specific(Point { x: 20.0, y: 20.0 }),
width: 300., decorations: false,
height: 150., ..window::Settings::default()
}, })
position: window::Position::Specific(Point { x: 20., y: 20. }), .style(|_, _| Appearance {
decorations: false, background_color: Color::TRANSPARENT,
..window::Settings::default() text_color: Color::WHITE,
}) })
.style(|_state, _theme| Appearance { .run_with(move || (WeatherPopup::new(resp, conf), Task::none()))
background_color: Color::TRANSPARENT,
text_color: Color::default(),
})
.run_with(move || (WeatherPopup::new(resp, conf), Task::none()))
} }
/// Convert OWM icon codes (e.g. "01d", "09n") to Nerd Font weather glyphs.
fn icon_to_nerd_font(code: &str) -> String { fn icon_to_nerd_font(code: &str) -> String {
match code { match code {
"01d" => "", // clear day "01d" => "",
"01n" => "", // clear night "01n" => "",
"02d" | "02n" => "", // few clouds "02d" | "02n" => "",
"03d" | "03n" => "", // scattered clouds "03d" | "03n" => "",
"04d" | "04n" => "", // broken clouds "04d" | "04n" => "",
"09d" | "09n" => "", // shower rain "09d" | "09n" => "",
"10d" | "10n" => "", // rain "10d" | "10n" => "",
"11d" | "11n" => "", // thunderstorm "11d" | "11n" => "",
"13d" | "13n" => "", // snow "13d" | "13n" => "",
"50d" | "50n" => "", // mist "50d" | "50n" => "",
_ => "", _ => "",
} }
.into() .into()

View File

@ -1,44 +0,0 @@
use slint::SharedString;
slint::include_modules!();
/// Create and show the UI window populated with weather data.
pub fn show_popup(
city: String,
country: String,
weather_main: String,
weather_description: String,
icon_code: String,
temperature: String,
unit: char,
) -> Result<(), Box<dyn std::error::Error>> {
let ui = MainWindow::new()?;
ui.set_city(SharedString::from(city));
ui.set_country(SharedString::from(country));
ui.set_weather_main(SharedString::from(weather_main));
ui.set_weather_description(SharedString::from(weather_description));
ui.set_weather_icon(SharedString::from(icon_to_nerd_font(&icon_code)));
ui.set_temperature(SharedString::from(temperature));
ui.set_unit(SharedString::from(unit));
ui.run()?;
Ok(())
}
/// Convert OWM icon codes (e.g. "01d", "09n") to Nerd Font weather glyphs.
fn icon_to_nerd_font(code: &str) -> String {
match code {
"01d" => "", // clear day
"01n" => "", // clear night
"02d" | "02n" => "", // few clouds
"03d" | "03n" => "", // scattered clouds
"04d" | "04n" => "", // broken clouds
"09d" | "09n" => "", // shower rain
"10d" | "10n" => "", // rain
"11d" | "11n" => "", // thunderstorm
"13d" | "13n" => "", // snow
"50d" | "50n" => "", // mist
_ => "",
}
.into()
}

View File

@ -1,139 +0,0 @@
export component MainWindow inherits Window {
in property <string> city;
in property <string> country;
in property <string> weather_main;
in property <string> weather_description;
in property <string> weather_icon;
in property <string> temperature;
in property <string> unit;
always-on-top: true;
no-frame: true;
width: 300px;
height: 124px;
background: transparent;
default-font-family: "IosevkaTermSlab Nerd Font Mono";
weather_popup := Rectangle {
width: 100%;
height: 100%;
border-radius: 12px;
background: @linear-gradient(160deg, #236a8bc7, #c5edff5c);
drop-shadow-blur: 15px;
drop-shadow-color: #00000066;
VerticalLayout {
spacing: 2px; // Consistent vertical spacing
horizontal-stretch: 1.0;
padding: 12px; // Consistent padding throughout
/* Top: City */
Text {
text: city + ", " + country;
font-size: 14px;
color: #fff;
//font-family:
horizontal-alignment: TextHorizontalAlignment.left;
height: 18px; // Fixed height for consistent spacing
font-weight: 600;
}
/* Middle: icon on left, temp on right */
HorizontalLayout {
horizontal-stretch: 1.0;
height: 50px; // Fixed height for main content area
padding: 0px;
/* Icon */
Text {
text: weather_icon;
font-size: 40px;
horizontal-alignment: TextHorizontalAlignment.left;
color: #fff;
vertical-alignment: TextVerticalAlignment.center;
width: 46px; // Fixed width for consistent alignment
}
/* Flexible spacer to help with alignment */
Rectangle {
horizontal-stretch: 1.0;
background: transparent;
}
/* Temperature group */
HorizontalLayout {
spacing: 4px; // Tighter spacing for temperature elements
padding: 0px;
//height: 50px;
Text {
text: temperature;
font-size: 40px;
color: #fff;
vertical-alignment: TextVerticalAlignment.center;
font-weight: 900;
}
VerticalLayout {
height: 50px; // Match temperature text height
padding: 0px;
spacing: 0px;
Text {
text: "o";
font-size: 16px;
color: #fff;
horizontal-alignment: TextHorizontalAlignment.left;
vertical-alignment: TextVerticalAlignment.center;
height: 25px;
font-weight: 800;
}
Text {
text: unit;
font-size: 16px;
color: #fff;
horizontal-alignment: TextHorizontalAlignment.left;
vertical-alignment: TextVerticalAlignment.center;
height: 20px;
width: 20px;
font-weight: 800;
}
}
}
}
/* Bottom: summary + description */
HorizontalLayout {
spacing: 8px;
horizontal-stretch: 1.0;
height: 32px;
padding: 0px;
/* Summary */
Text {
text: weather_main;
font-size: 18px;
color: #fff;
vertical-alignment: TextVerticalAlignment.top;
horizontal-alignment: TextHorizontalAlignment.left;
width: 40%; // Fixed percentage width for consistent layout
font-weight: 800;
}
/* Description */
Text {
text: weather_description;
font-size: 12px;
color: #ffffffcc;
horizontal-alignment: TextHorizontalAlignment.right;
vertical-alignment: TextVerticalAlignment.top;
horizontal-stretch: 1.0;
wrap: word-wrap;
height: 100%;
font-weight: 600;
}
}
}
}
}