add icon to executable
This commit is contained in:
parent
510b28bde5
commit
143a96463c
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -1310,6 +1310,8 @@ dependencies = [
|
||||
"serde",
|
||||
"toml",
|
||||
"ureq",
|
||||
"windows 0.43.0",
|
||||
"winres",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2928,6 +2930,15 @@ dependencies = [
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winres"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
|
||||
dependencies = [
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wio"
|
||||
version = "0.2.2"
|
||||
|
14
Cargo.toml
14
Cargo.toml
@ -2,6 +2,10 @@
|
||||
name = "ktu-timetable"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 'z' # Optimize for size
|
||||
@ -23,3 +27,13 @@ directories-next = "2.0.0"
|
||||
toml = "0.5.11"
|
||||
serde = { version = "1.0.152", features = ["derive"]}
|
||||
lazy_static = "1.4.0"
|
||||
winres = "0.1.12"
|
||||
|
||||
[dependencies.windows]
|
||||
version = "0.43.0"
|
||||
features = [
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_Foundation",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_Graphics_Gdi"
|
||||
]
|
BIN
assets/icon.ase
Normal file
BIN
assets/icon.ase
Normal file
Binary file not shown.
BIN
assets/icon.ico
BIN
assets/icon.ico
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 50 KiB |
BIN
assets/icon.png
BIN
assets/icon.png
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB |
BIN
assets/small-icon.ase
Normal file
BIN
assets/small-icon.ase
Normal file
Binary file not shown.
9
build.rs
Normal file
9
build.rs
Normal file
@ -0,0 +1,9 @@
|
||||
extern crate winres;
|
||||
|
||||
fn main() {
|
||||
if cfg!(target_os = "windows") {
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon_with_id("assets/icon.ico", "window-icon");
|
||||
res.compile().unwrap();
|
||||
}
|
||||
}
|
15
src/environment.rs
Normal file
15
src/environment.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use crate::{timetable::{TimetableGetter, BlockingTimetableGetter}, config::{ConfigStore, TomlConfigStore}};
|
||||
|
||||
pub struct Environment {
|
||||
pub timetable_getter: Box<dyn TimetableGetter>,
|
||||
pub config_store: Box<dyn ConfigStore>
|
||||
}
|
||||
|
||||
impl Default for Environment {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
config_store: Box::new(TomlConfigStore::default()),
|
||||
timetable_getter: Box::new(BlockingTimetableGetter::default())
|
||||
}
|
||||
}
|
||||
}
|
37
src/main.rs
37
src/main.rs
@ -5,13 +5,14 @@ mod app;
|
||||
mod config;
|
||||
mod events_table;
|
||||
mod utils;
|
||||
mod platforms;
|
||||
mod environment;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use app::MainApp;
|
||||
use config::TomlConfigStore;
|
||||
use eframe::egui;
|
||||
use environment::Environment;
|
||||
use timetable::BlockingTimetableGetter;
|
||||
|
||||
// TODO: show errors when loading config
|
||||
@ -19,7 +20,7 @@ use timetable::BlockingTimetableGetter;
|
||||
// TODO: use "confy" for config loading?
|
||||
// TODO: Setup pipeline
|
||||
|
||||
fn main() -> Result<(), ureq::Error> {
|
||||
fn main() {
|
||||
let config_store = TomlConfigStore::default();
|
||||
// let config_store = MemoryConfigStore::new(Config {
|
||||
// vidko: None//Some("E1810".into())
|
||||
@ -41,29 +42,9 @@ fn main() -> Result<(), ureq::Error> {
|
||||
// ]
|
||||
// });
|
||||
|
||||
let mut native_options = eframe::NativeOptions::default();
|
||||
native_options.decorated = true;
|
||||
native_options.resizable = true;
|
||||
native_options.min_window_size = Some(egui::vec2(480.0, 320.0));
|
||||
native_options.initial_window_size = Some(egui::vec2(500.0, 320.0));
|
||||
native_options.icon_data = Some(eframe::IconData {
|
||||
rgba: image::load_from_memory(include_bytes!("../assets/icon.png"))
|
||||
.expect("Failed to load icon")
|
||||
.into_rgb8()
|
||||
.into_raw(),
|
||||
width: 32,
|
||||
height: 32,
|
||||
});
|
||||
let mut app = MainApp::new(Box::new(config_store), Box::new(timetable_getter));
|
||||
|
||||
eframe::run_native(
|
||||
"KTU timetable",
|
||||
native_options,
|
||||
Box::new(move |cc| {
|
||||
app.init(cc);
|
||||
Box::new(app)
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
platforms::run_windows_app(Environment {
|
||||
timetable_getter: Box::new(timetable_getter),
|
||||
config_store: Box::new(config_store)
|
||||
})
|
||||
}
|
||||
|
||||
|
3
src/platforms/mod.rs
Normal file
3
src/platforms/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod win;
|
||||
|
||||
pub use win::run_windows_app;
|
132
src/platforms/win.rs
Normal file
132
src/platforms/win.rs
Normal file
@ -0,0 +1,132 @@
|
||||
use eframe::IconData;
|
||||
use windows::{
|
||||
w,
|
||||
Win32::{
|
||||
Graphics::Gdi::{
|
||||
CreateCompatibleDC, DeleteDC, GetDIBits, GetObjectA, SelectObject, BITMAP, BITMAPINFO,
|
||||
BITMAPINFOHEADER, BI_RGB, DIB_RGB_COLORS,
|
||||
},
|
||||
System::LibraryLoader::GetModuleHandleW,
|
||||
UI::WindowsAndMessaging::{
|
||||
GetIconInfo, LoadImageW, HICON, ICONINFO, IMAGE_ICON, LR_DEFAULTCOLOR,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{environment::Environment, app::MainApp};
|
||||
|
||||
// Yoinked from https://github.com/emilk/egui/issues/920#issuecomment-1364446538
|
||||
fn load_app_icon() -> IconData {
|
||||
let (mut buffer, width, height) = unsafe {
|
||||
let h_instance = GetModuleHandleW(None).expect("Failed to get HINSTANCE");
|
||||
let icon = LoadImageW(
|
||||
h_instance,
|
||||
w!("window-icon"),
|
||||
IMAGE_ICON,
|
||||
64,
|
||||
64,
|
||||
LR_DEFAULTCOLOR,
|
||||
)
|
||||
.expect("Failed to load icon");
|
||||
|
||||
let mut icon_info = ICONINFO::default();
|
||||
let res = GetIconInfo(HICON(icon.0), &mut icon_info as *mut _).as_bool();
|
||||
if !res {
|
||||
panic!("Failed to load icon info");
|
||||
}
|
||||
|
||||
let mut bitmap = BITMAP::default();
|
||||
GetObjectA(
|
||||
icon_info.hbmColor,
|
||||
std::mem::size_of::<BITMAP>() as i32,
|
||||
Some(&mut bitmap as *mut _ as *mut _),
|
||||
);
|
||||
|
||||
let width = bitmap.bmWidth;
|
||||
let height = bitmap.bmHeight;
|
||||
|
||||
let b_size = (width * height * 4) as usize;
|
||||
let mut buffer = Vec::<u8>::with_capacity(b_size);
|
||||
|
||||
let h_dc = CreateCompatibleDC(None);
|
||||
let h_bitmap = SelectObject(h_dc, icon_info.hbmColor);
|
||||
|
||||
let mut bitmap_info = BITMAPINFO::default();
|
||||
bitmap_info.bmiHeader.biSize = std::mem::size_of::<BITMAPINFOHEADER>() as u32;
|
||||
bitmap_info.bmiHeader.biWidth = width;
|
||||
bitmap_info.bmiHeader.biHeight = height;
|
||||
bitmap_info.bmiHeader.biPlanes = 1;
|
||||
bitmap_info.bmiHeader.biBitCount = 32;
|
||||
bitmap_info.bmiHeader.biCompression = BI_RGB;
|
||||
bitmap_info.bmiHeader.biSizeImage = 0;
|
||||
|
||||
let res = GetDIBits(
|
||||
h_dc,
|
||||
icon_info.hbmColor,
|
||||
0,
|
||||
height as u32,
|
||||
Some(buffer.spare_capacity_mut().as_mut_ptr() as *mut _),
|
||||
&mut bitmap_info as *mut _,
|
||||
DIB_RGB_COLORS,
|
||||
);
|
||||
if res == 0 {
|
||||
panic!("Failed to get RGB DI bits");
|
||||
}
|
||||
|
||||
SelectObject(h_dc, h_bitmap);
|
||||
DeleteDC(h_dc);
|
||||
|
||||
assert_eq!(
|
||||
bitmap_info.bmiHeader.biSizeImage as usize,
|
||||
b_size,
|
||||
"returned biSizeImage must equal to b_size"
|
||||
);
|
||||
|
||||
// set the new size
|
||||
buffer.set_len(bitmap_info.bmiHeader.biSizeImage as usize);
|
||||
|
||||
(buffer, width as u32, height as u32)
|
||||
};
|
||||
|
||||
// RGBA -> BGRA
|
||||
for pixel in buffer.as_mut_slice().chunks_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
|
||||
// Flip the image vertically
|
||||
let row_size = width as usize * 4; // number of pixels in each row
|
||||
let row_count = buffer.len() as usize / row_size; // number of rows in the image
|
||||
for row in 0..row_count / 2 {
|
||||
// loop through half of the rows
|
||||
let start = row * row_size; // index of the start of the current row
|
||||
let end = (row_count - row - 1) * row_size; // index of the end of the current row
|
||||
for i in 0..row_size {
|
||||
buffer.swap(start + i, end + i);
|
||||
}
|
||||
}
|
||||
|
||||
IconData {
|
||||
rgba: buffer,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_windows_app(env: Environment) {
|
||||
let mut native_options = eframe::NativeOptions::default();
|
||||
native_options.decorated = true;
|
||||
native_options.resizable = true;
|
||||
native_options.min_window_size = Some(egui::vec2(480.0, 320.0));
|
||||
native_options.initial_window_size = Some(egui::vec2(500.0, 320.0));
|
||||
native_options.icon_data = Some(load_app_icon());
|
||||
let mut app = MainApp::new(env.config_store, env.timetable_getter);
|
||||
|
||||
eframe::run_native(
|
||||
"KTU timetable",
|
||||
native_options,
|
||||
Box::new(move |cc| {
|
||||
app.init(cc);
|
||||
Box::new(app)
|
||||
})
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user