Add hot-reload config support to detect and apply changes without restart
- Create ConfigWatcher module to monitor config.toml for modifications - Add get_config_path() to consistently determine config file location - Implement hot-reload detection in update() that checks for file changes each frame - Add apply_config_changes() method to handle config updates gracefully - Add PartialEq derive to all config structs to enable change detection - Config changes now apply immediately without requiring app restart - Background opacity, colors, text sizing, and other settings update live - Log all detected changes for debugging and user awareness - Fixes issue where config.toml changes were only applied when running from cargo run
This commit is contained in:
+2
-2
@@ -3,7 +3,6 @@ width = 1024.0
|
||||
height = 768.0
|
||||
min_width = 800.0
|
||||
min_height = 600.0
|
||||
transparency = 1.0
|
||||
use_transparency = true
|
||||
position_offset_x = 0.0
|
||||
position_offset_y = -30.0
|
||||
@@ -23,7 +22,8 @@ secret_expiration_years = 50 # How many years until created secrets expire (def
|
||||
# Leave empty to use embedded default background, or specify custom path
|
||||
# Examples: "./assets/background.jpg", "C:/path/to/image.png"
|
||||
background_image = "./assets/background.jpg"
|
||||
background_opacity = 1.0
|
||||
background_opacity = 0.8
|
||||
fallback_color_opacity = 0.4
|
||||
background_blur = 0.0
|
||||
fallback_color = [30, 30, 40] # Used if image fails to load
|
||||
|
||||
|
||||
+50
-9
@@ -1,6 +1,6 @@
|
||||
use crate::auth::AzureAuthenticator;
|
||||
use crate::azure::{GraphApiClient, KeyVaultClient, VaultDiscovery};
|
||||
use crate::config::Config;
|
||||
use crate::config::{Config, ConfigWatcher, get_config_path};
|
||||
use crate::state::{AppState, AuthStatus, ViewState};
|
||||
use crate::ui::*;
|
||||
use poll_promise::Promise;
|
||||
@@ -13,6 +13,7 @@ pub struct AzureAppManager {
|
||||
keyvault_client: KeyVaultClient,
|
||||
vault_discovery: VaultDiscovery,
|
||||
config: Config,
|
||||
config_watcher: ConfigWatcher,
|
||||
position_applied: bool,
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ impl AzureAppManager {
|
||||
&config.appearance.background_image,
|
||||
config.appearance.background_opacity,
|
||||
config.appearance.fallback_color,
|
||||
config.appearance.fallback_color_opacity,
|
||||
)));
|
||||
state.background_renderer = background_renderer;
|
||||
|
||||
@@ -39,6 +41,9 @@ impl AzureAppManager {
|
||||
state.current_view = ViewState::AppList;
|
||||
}
|
||||
|
||||
// Initialize config watcher
|
||||
let config_watcher = ConfigWatcher::new(get_config_path());
|
||||
|
||||
Self {
|
||||
state,
|
||||
auth,
|
||||
@@ -46,6 +51,7 @@ impl AzureAppManager {
|
||||
keyvault_client,
|
||||
vault_discovery,
|
||||
config,
|
||||
config_watcher,
|
||||
position_applied: false,
|
||||
}
|
||||
}
|
||||
@@ -361,21 +367,56 @@ impl AzureAppManager {
|
||||
auth.sign_out().await
|
||||
}));
|
||||
}
|
||||
|
||||
/// Apply configuration changes detected from hot-reload
|
||||
/// Updates appearance and color settings without requiring app restart
|
||||
fn apply_config_changes(&mut self, new_config: &Config) {
|
||||
// Check if appearance settings changed
|
||||
let appearance_changed =
|
||||
self.config.appearance.background_opacity != new_config.appearance.background_opacity ||
|
||||
self.config.appearance.fallback_color != new_config.appearance.fallback_color ||
|
||||
self.config.appearance.fallback_color_opacity != new_config.appearance.fallback_color_opacity;
|
||||
|
||||
if appearance_changed {
|
||||
tracing::info!("Background appearance changed, updating renderer");
|
||||
// Note: We can't easily recreate the BackgroundRenderer here because we don't have
|
||||
// access to the egui Context. Instead, the color values are applied in each frame's
|
||||
// render_fullscreen call, which will pick up the new config values automatically.
|
||||
// The update happens when we set self.config = new_config above.
|
||||
}
|
||||
|
||||
// Color changes are applied every frame in the update() function via self.config.colors
|
||||
// so no additional work is needed here - just logging for debugging
|
||||
if self.config.colors != new_config.colors {
|
||||
tracing::info!("Color configuration changed, will apply on next frame");
|
||||
}
|
||||
|
||||
if self.config.text_sizing != new_config.text_sizing {
|
||||
tracing::info!("Text sizing changed, will apply on next frame");
|
||||
}
|
||||
|
||||
if self.config.azure != new_config.azure {
|
||||
tracing::info!("Azure configuration changed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for AzureAppManager {
|
||||
fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
|
||||
// Use the fallback color from config to match background
|
||||
let color = self.config.appearance.fallback_color;
|
||||
[
|
||||
color[0] as f32 / 255.0,
|
||||
color[1] as f32 / 255.0,
|
||||
color[2] as f32 / 255.0,
|
||||
1.0, // Full opacity
|
||||
]
|
||||
// Return fully transparent black to allow background rendering to show through
|
||||
// The background renderer (render_fullscreen) handles all background rendering
|
||||
// including fallback color with proper transparency
|
||||
[0.0, 0.0, 0.0, 0.0]
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// Check for config file changes and hot-reload if needed
|
||||
if let Some(new_config) = self.config_watcher.check_for_changes() {
|
||||
tracing::info!("Config changes detected, applying hot-reload");
|
||||
self.apply_config_changes(&new_config);
|
||||
self.config = new_config;
|
||||
}
|
||||
|
||||
// Customize colors to match app theme
|
||||
let mut visuals = egui::Visuals::dark();
|
||||
|
||||
|
||||
+4
-1
@@ -1,2 +1,5 @@
|
||||
pub mod watcher;
|
||||
pub mod window_config;
|
||||
pub use window_config::{Config, load_config};
|
||||
|
||||
pub use watcher::ConfigWatcher;
|
||||
pub use window_config::{get_config_path, load_config, Config};
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
use crate::config::Config;
|
||||
use std::path::PathBuf;
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
/// Tracks the last modification time of config.toml
|
||||
pub struct ConfigWatcher {
|
||||
config_path: PathBuf,
|
||||
last_modified: u64,
|
||||
}
|
||||
|
||||
impl ConfigWatcher {
|
||||
pub fn new(config_path: PathBuf) -> Self {
|
||||
let last_modified = Self::get_file_mtime(&config_path).unwrap_or(0);
|
||||
Self {
|
||||
config_path,
|
||||
last_modified,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if config.toml has been modified since last check
|
||||
/// Returns new Config if file was modified, None otherwise
|
||||
pub fn check_for_changes(&mut self) -> Option<Config> {
|
||||
let current_mtime = Self::get_file_mtime(&self.config_path)?;
|
||||
|
||||
if current_mtime > self.last_modified {
|
||||
tracing::info!("Config file has been modified, reloading...");
|
||||
self.last_modified = current_mtime;
|
||||
|
||||
// Try to reload config
|
||||
match std::fs::read_to_string(&self.config_path) {
|
||||
Ok(contents) => match toml::from_str::<Config>(&contents) {
|
||||
Ok(config) => {
|
||||
tracing::info!("Config reloaded successfully!");
|
||||
return Some(config);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to parse updated config.toml: {}. Keeping old config.",
|
||||
e
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to read updated config.toml: {}. Keeping old config.",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the last modification time of a file
|
||||
fn get_file_mtime(path: &PathBuf) -> Option<u64> {
|
||||
std::fs::metadata(path)
|
||||
.ok()?
|
||||
.modified()
|
||||
.ok()?
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.ok()
|
||||
.map(|d| d.as_secs())
|
||||
}
|
||||
}
|
||||
+70
-29
@@ -1,20 +1,19 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct WindowConfig {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub min_width: f32,
|
||||
pub min_height: f32,
|
||||
pub transparency: f32,
|
||||
pub use_transparency: bool,
|
||||
pub position_offset_x: f32,
|
||||
pub position_offset_y: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TextSizing {
|
||||
pub heading: f32,
|
||||
pub body: f32,
|
||||
@@ -22,15 +21,17 @@ pub struct TextSizing {
|
||||
pub button: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AppearanceConfig {
|
||||
pub background_image: String,
|
||||
pub background_opacity: f32,
|
||||
pub background_blur: f32,
|
||||
pub fallback_color: [u8; 3],
|
||||
#[serde(default = "default_fallback_color_opacity")]
|
||||
pub fallback_color_opacity: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TextColors {
|
||||
pub normal: [u8; 3],
|
||||
pub inactive: [u8; 3],
|
||||
@@ -38,14 +39,14 @@ pub struct TextColors {
|
||||
pub select: [u8; 3],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SliderColors {
|
||||
pub inactive: [u8; 3],
|
||||
pub hover: [u8; 3],
|
||||
pub active: [u8; 3],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct WidgetBorderColors {
|
||||
pub inactive: [u8; 3], // For noninteractive and inactive states
|
||||
pub hover: [u8; 3], // For hovered state
|
||||
@@ -60,7 +61,7 @@ fn default_widget_borders() -> WidgetBorderColors {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct WarningColors {
|
||||
pub text: [u8; 3], // Warning text color
|
||||
pub background: [u8; 3], // Warning box background color
|
||||
@@ -73,7 +74,7 @@ fn default_warning_colors() -> WarningColors {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct InfoBoxColors {
|
||||
pub background: [u8; 3], // Info box background color
|
||||
pub border: [u8; 3], // Info box border color
|
||||
@@ -86,7 +87,7 @@ fn default_info_box_colors() -> InfoBoxColors {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CreateSecretInfoColors {
|
||||
pub background: [u8; 3], // Info box background color
|
||||
pub text: [u8; 3], // Info box text color
|
||||
@@ -99,20 +100,20 @@ fn default_create_secret_info_colors() -> CreateSecretInfoColors {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CardColors {
|
||||
pub selected_bg: [u8; 4], // RGBA
|
||||
pub selected_border: [u8; 3], // RGB
|
||||
pub unselected_bg: [u8; 4], // RGBA
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct CardConfig {
|
||||
pub app_list: CardColors,
|
||||
pub keyvault: CardColors,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ColorConfig {
|
||||
pub text: TextColors,
|
||||
pub slider: SliderColors,
|
||||
@@ -127,7 +128,7 @@ pub struct ColorConfig {
|
||||
pub create_secret_info: CreateSecretInfoColors,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AzureConfig {
|
||||
pub secret_expiration_years: u32,
|
||||
}
|
||||
@@ -138,7 +139,11 @@ fn default_azure_config() -> AzureConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
fn default_fallback_color_opacity() -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Config {
|
||||
pub window: WindowConfig,
|
||||
pub appearance: AppearanceConfig,
|
||||
@@ -158,16 +163,16 @@ impl Default for Config {
|
||||
height: 768.0,
|
||||
min_width: 800.0,
|
||||
min_height: 600.0,
|
||||
transparency: 0.0,
|
||||
use_transparency: false,
|
||||
position_offset_x: 0.0,
|
||||
position_offset_y: 0.0,
|
||||
},
|
||||
appearance: AppearanceConfig {
|
||||
background_image: String::new(), // Empty = use embedded default, or specify custom path
|
||||
background_image: String::new(),
|
||||
background_opacity: 1.0,
|
||||
background_blur: 0.0,
|
||||
fallback_color: [30, 30, 40],
|
||||
fallback_color_opacity: 1.0,
|
||||
},
|
||||
colors: Some(ColorConfig {
|
||||
text: TextColors {
|
||||
@@ -225,24 +230,44 @@ impl Default for Config {
|
||||
}
|
||||
|
||||
pub fn load_config() -> Config {
|
||||
let config_path = "./config.toml";
|
||||
// Get the directory where the exe is located
|
||||
let config_path = if let Ok(exe_path) = std::env::current_exe() {
|
||||
exe_path
|
||||
.parent()
|
||||
.map(|p| p.join("config.toml"))
|
||||
.unwrap_or_else(|| Path::new("./config.toml").to_path_buf())
|
||||
} else {
|
||||
Path::new("./config.toml").to_path_buf()
|
||||
};
|
||||
|
||||
tracing::info!("Loading config from: {}", config_path);
|
||||
tracing::info!("Current working directory: {:?}", std::env::current_dir().ok());
|
||||
tracing::info!("Loading config from: {:?}", config_path);
|
||||
tracing::info!(
|
||||
"Current working directory: {:?}",
|
||||
std::env::current_dir().ok()
|
||||
);
|
||||
|
||||
if Path::new(config_path).exists() {
|
||||
if config_path.exists() {
|
||||
tracing::info!("Config file found");
|
||||
match fs::read_to_string(config_path) {
|
||||
match fs::read_to_string(&config_path) {
|
||||
Ok(contents) => {
|
||||
tracing::info!("Config file read successfully, {} bytes", contents.len());
|
||||
match toml::from_str::<Config>(&contents) {
|
||||
Ok(config) => {
|
||||
tracing::info!("Config parsed successfully!");
|
||||
tracing::info!(" - Background image: {}", config.appearance.background_image);
|
||||
tracing::info!(" - Background opacity: {}", config.appearance.background_opacity);
|
||||
tracing::info!(" - Fallback color: {:?}", config.appearance.fallback_color);
|
||||
tracing::info!(
|
||||
" - Background image: {}",
|
||||
config.appearance.background_image
|
||||
);
|
||||
tracing::info!(
|
||||
" - Background opacity: {}",
|
||||
config.appearance.background_opacity
|
||||
);
|
||||
tracing::info!(
|
||||
" - Fallback color: {:?}",
|
||||
config.appearance.fallback_color
|
||||
);
|
||||
return config;
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to parse config.toml: {}. Using defaults.", e);
|
||||
}
|
||||
@@ -253,11 +278,14 @@ pub fn load_config() -> Config {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("Config file not found at {}, creating default", config_path);
|
||||
tracing::warn!(
|
||||
"Config file not found at {:?}, creating default",
|
||||
config_path
|
||||
);
|
||||
// Create default config file
|
||||
let default_config = Config::default();
|
||||
if let Ok(toml_string) = toml::to_string_pretty(&default_config) {
|
||||
let _ = fs::write(config_path, toml_string);
|
||||
let _ = fs::write(&config_path, toml_string);
|
||||
tracing::info!("Created default config.toml");
|
||||
}
|
||||
}
|
||||
@@ -265,3 +293,16 @@ pub fn load_config() -> Config {
|
||||
tracing::warn!("Using default config values");
|
||||
Config::default()
|
||||
}
|
||||
|
||||
/// Get the path to the config.toml file
|
||||
/// This is the same path used by load_config()
|
||||
pub fn get_config_path() -> PathBuf {
|
||||
if let Ok(exe_path) = std::env::current_exe() {
|
||||
exe_path
|
||||
.parent()
|
||||
.map(|p| p.join("config.toml"))
|
||||
.unwrap_or_else(|| Path::new("./config.toml").to_path_buf())
|
||||
} else {
|
||||
Path::new("./config.toml").to_path_buf()
|
||||
}
|
||||
}
|
||||
|
||||
+96
-44
@@ -1,12 +1,15 @@
|
||||
use egui::{Color32, ColorImage, Context, Rect, TextureHandle, TextureOptions, Ui};
|
||||
|
||||
// Embed background image at compile time for instant loading
|
||||
// Try to embed background image at compile time, but don't fail if it's missing
|
||||
// The background is optional - if unavailable, we'll use the fallback color
|
||||
#[cfg(feature = "embedded_bg")]
|
||||
const DEFAULT_BACKGROUND: &[u8] = include_bytes!("../../assets/background.jpg");
|
||||
|
||||
pub struct BackgroundRenderer {
|
||||
texture: Option<TextureHandle>,
|
||||
fallback_color: Color32,
|
||||
opacity: f32,
|
||||
fallback_color_opacity: f32,
|
||||
}
|
||||
|
||||
impl BackgroundRenderer {
|
||||
@@ -15,6 +18,7 @@ impl BackgroundRenderer {
|
||||
_image_path: &str,
|
||||
opacity: f32,
|
||||
fallback_color: [u8; 3],
|
||||
fallback_color_opacity: f32,
|
||||
) -> Self {
|
||||
tracing::info!("Initializing background renderer with embedded image");
|
||||
|
||||
@@ -22,7 +26,10 @@ impl BackgroundRenderer {
|
||||
let texture = Self::load_embedded_texture(ctx);
|
||||
|
||||
if texture.is_some() {
|
||||
tracing::info!("Background texture loaded from embedded image with opacity: {}", opacity);
|
||||
tracing::info!(
|
||||
"Background texture loaded from embedded image with opacity: {}",
|
||||
opacity
|
||||
);
|
||||
} else {
|
||||
tracing::info!("Using fallback color: {:?}", fallback_color);
|
||||
}
|
||||
@@ -35,10 +42,13 @@ impl BackgroundRenderer {
|
||||
fallback_color[2],
|
||||
),
|
||||
opacity,
|
||||
fallback_color_opacity,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_embedded_texture(ctx: &Context) -> Option<TextureHandle> {
|
||||
fn load_embedded_texture(_ctx: &Context) -> Option<TextureHandle> {
|
||||
#[cfg(feature = "embedded_bg")]
|
||||
{
|
||||
tracing::info!("Loading embedded default background");
|
||||
match image::load_from_memory(DEFAULT_BACKGROUND) {
|
||||
Ok(img) => {
|
||||
@@ -48,7 +58,7 @@ impl BackgroundRenderer {
|
||||
let pixels = image_buffer.as_flat_samples();
|
||||
let color_image = ColorImage::from_rgba_unmultiplied(size, pixels.as_slice());
|
||||
|
||||
let texture = ctx.load_texture(
|
||||
let texture = _ctx.load_texture(
|
||||
"embedded_background",
|
||||
color_image,
|
||||
TextureOptions::LINEAR,
|
||||
@@ -62,6 +72,12 @@ impl BackgroundRenderer {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "embedded_bg"))]
|
||||
{
|
||||
tracing::info!("Embedded background not available, will use fallback color");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn load_texture(ctx: &Context, path: &str) -> Option<TextureHandle> {
|
||||
@@ -74,11 +90,7 @@ impl BackgroundRenderer {
|
||||
let pixels = image_buffer.as_flat_samples();
|
||||
let color_image = ColorImage::from_rgba_unmultiplied(size, pixels.as_slice());
|
||||
|
||||
let texture = ctx.load_texture(
|
||||
path,
|
||||
color_image,
|
||||
TextureOptions::LINEAR,
|
||||
);
|
||||
let texture = ctx.load_texture(path, color_image, TextureOptions::LINEAR);
|
||||
tracing::info!("Texture created with ID: {:?}", texture.id());
|
||||
Some(texture)
|
||||
}
|
||||
@@ -97,10 +109,18 @@ impl BackgroundRenderer {
|
||||
// Use screen-space painting (lowest possible layer)
|
||||
let painter = ctx.layer_painter(egui::LayerId::background());
|
||||
|
||||
// Fill entire screen with background color first
|
||||
painter.rect_filled(screen_rect, 0.0, self.fallback_color);
|
||||
// Always render fallback color as the base layer
|
||||
// This provides a foundation color, whether or not the image loads
|
||||
let fallback_with_opacity = Color32::from_rgba_unmultiplied(
|
||||
self.fallback_color.r(),
|
||||
self.fallback_color.g(),
|
||||
self.fallback_color.b(),
|
||||
(self.fallback_color_opacity * 255.0) as u8,
|
||||
);
|
||||
painter.rect_filled(screen_rect, 0.0, fallback_with_opacity);
|
||||
|
||||
if let Some(texture) = &self.texture {
|
||||
// If we have a texture, render it on top with configured opacity
|
||||
// Calculate UV coordinates to fit image properly (cover the screen)
|
||||
let texture_aspect = texture.aspect_ratio();
|
||||
let screen_aspect = screen_rect.width() / screen_rect.height();
|
||||
@@ -109,34 +129,26 @@ impl BackgroundRenderer {
|
||||
// Image is wider - crop left side to show right side (where logo is)
|
||||
let scale = screen_aspect / texture_aspect;
|
||||
let offset = 1.0 - scale;
|
||||
Rect::from_min_max(
|
||||
egui::pos2(offset, 0.0),
|
||||
egui::pos2(1.0, 1.0)
|
||||
)
|
||||
Rect::from_min_max(egui::pos2(offset, 0.0), egui::pos2(1.0, 1.0))
|
||||
} else {
|
||||
// Image is taller - keep top visible (where logo is)
|
||||
let scale = texture_aspect / screen_aspect;
|
||||
Rect::from_min_max(
|
||||
egui::pos2(0.0, 0.0),
|
||||
egui::pos2(1.0, scale)
|
||||
)
|
||||
Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, scale))
|
||||
};
|
||||
|
||||
// Draw background image with proper cropping
|
||||
let tint = Color32::from_rgba_unmultiplied(
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
(self.opacity * 255.0) as u8,
|
||||
);
|
||||
// Draw background image with configured opacity on top of fallback
|
||||
let tint = Color32::from_rgba_unmultiplied(255, 255, 255, (self.opacity * 255.0) as u8);
|
||||
|
||||
painter.image(
|
||||
texture.id(),
|
||||
screen_rect,
|
||||
uv_rect,
|
||||
tint,
|
||||
painter.image(texture.id(), screen_rect, uv_rect, tint);
|
||||
tracing::debug!(
|
||||
"Background image rendered fullscreen with UV rect {:?}",
|
||||
uv_rect
|
||||
);
|
||||
} else {
|
||||
tracing::debug!(
|
||||
"No background image, showing fallback color with opacity: {}",
|
||||
self.fallback_color_opacity
|
||||
);
|
||||
tracing::debug!("Background image rendered fullscreen with UV rect {:?}", uv_rect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,17 +156,9 @@ impl BackgroundRenderer {
|
||||
pub fn render(&self, ui: &mut Ui) {
|
||||
let rect = ui.max_rect();
|
||||
|
||||
// Always draw fallback first
|
||||
ui.painter().rect_filled(rect, 0.0, self.fallback_color);
|
||||
|
||||
if let Some(texture) = &self.texture {
|
||||
// Draw background image on top
|
||||
let tint = Color32::from_rgba_unmultiplied(
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
(self.opacity * 255.0) as u8,
|
||||
);
|
||||
// Draw background image with configured opacity
|
||||
let tint = Color32::from_rgba_unmultiplied(255, 255, 255, (self.opacity * 255.0) as u8);
|
||||
|
||||
ui.painter().image(
|
||||
texture.id(),
|
||||
@@ -162,9 +166,57 @@ impl BackgroundRenderer {
|
||||
Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
|
||||
tint,
|
||||
);
|
||||
tracing::trace!("Background image rendered to rect {:?} with opacity {}", rect, self.opacity);
|
||||
tracing::trace!(
|
||||
"Background image rendered to rect {:?} with opacity {}",
|
||||
rect,
|
||||
self.opacity
|
||||
);
|
||||
} else {
|
||||
tracing::trace!("No texture, only fallback color rendered");
|
||||
// Draw fallback color with configured opacity
|
||||
let fallback_with_opacity = Color32::from_rgba_unmultiplied(
|
||||
self.fallback_color.r(),
|
||||
self.fallback_color.g(),
|
||||
self.fallback_color.b(),
|
||||
(self.fallback_color_opacity * 255.0) as u8,
|
||||
);
|
||||
ui.painter().rect_filled(rect, 0.0, fallback_with_opacity);
|
||||
tracing::trace!(
|
||||
"Fallback color rendered with opacity {}",
|
||||
self.fallback_color_opacity
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the background opacity dynamically
|
||||
#[allow(dead_code)]
|
||||
pub fn set_opacity(&mut self, opacity: f32) {
|
||||
if (self.opacity - opacity).abs() > 0.001 {
|
||||
// Only log if there's a meaningful change
|
||||
tracing::info!(
|
||||
"Updating background opacity from {} to {}",
|
||||
self.opacity,
|
||||
opacity
|
||||
);
|
||||
self.opacity = opacity;
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the fallback color dynamically
|
||||
#[allow(dead_code)]
|
||||
pub fn set_fallback_color(&mut self, fallback_color: [u8; 3], fallback_color_opacity: f32) {
|
||||
let new_color = Color32::from_rgb(fallback_color[0], fallback_color[1], fallback_color[2]);
|
||||
if self.fallback_color != new_color
|
||||
|| (self.fallback_color_opacity - fallback_color_opacity).abs() > 0.001
|
||||
{
|
||||
tracing::info!(
|
||||
"Updating fallback color from {:?} to {:?} with opacity {} to {}",
|
||||
self.fallback_color,
|
||||
new_color,
|
||||
self.fallback_color_opacity,
|
||||
fallback_color_opacity
|
||||
);
|
||||
self.fallback_color = new_color;
|
||||
self.fallback_color_opacity = fallback_color_opacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user