feat: new ui prototype

This commit is contained in:
MeexReay 2025-06-26 16:27:02 +03:00
parent 56f66232eb
commit 47342294e8
8 changed files with 1030 additions and 41 deletions

878
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@ notify-rust = { version = "4.11.7", optional = true }
gdk-pixbuf = { version = "0.3.0", optional = true } # DO NOT UPDATE
winapi = { version = "0.3.9", optional = true, features = ["wincon", "winuser"] }
tungstenite = "0.27.0"
reqwest = "0.12.20"
[build-dependencies]
winresource = { version = "0.1.20", optional = true }

View File

@ -59,6 +59,8 @@ pub struct Config {
pub proxy: Option<String>,
#[serde(default = "default_true")]
pub notifications_enabled: bool,
#[serde(default = "default_true")]
pub new_ui_enabled: bool,
#[serde(default)]
pub debug_logs: bool,
}
@ -148,6 +150,8 @@ pub struct Args {
#[arg(long)]
pub notifications_enabled: Option<bool>,
#[arg(long)]
pub new_ui_enabled: Option<bool>,
#[arg(long)]
pub proxy: Option<String>,
#[arg(long)]
pub debug_logs: bool,
@ -200,6 +204,9 @@ impl Args {
if let Some(v) = self.notifications_enabled {
config.notifications_enabled = v
}
if let Some(v) = self.new_ui_enabled {
config.new_ui_enabled = v
}
if self.debug_logs {
config.debug_logs = true
}

View File

@ -1,4 +1,5 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::sync::{atomic::Ordering, mpsc::channel, Arc, RwLock};
use std::thread;
@ -6,7 +7,8 @@ use std::time::{Duration, SystemTime};
use chrono::Local;
use gtk4::{self as gtk};
use gtk4::ffi::GtkGrid;
use gtk4 as gtk;
use gtk::gdk::{Cursor, Display, Texture};
use gtk::gdk_pixbuf::{Pixbuf, PixbufAnimation, PixbufLoader};
@ -43,6 +45,8 @@ struct UiModel {
notifications: Arc<RwLock<Vec<libnotify::Notification>>>,
#[cfg(all(not(feature = "libnotify"), not(feature = "notify-rust")))]
notifications: Arc<RwLock<Vec<String>>>,
default_avatar: Pixbuf,
avatars: Arc<RwLock<HashMap<u64, Pixbuf>>>,
}
thread_local!(
@ -203,6 +207,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
gui_usize_entry_setting!("Konata Size", konata_size, ctx, settings_vbox);
let remove_gui_shit_entry =
gui_checkbox_setting!("Remove Gui Shit", remove_gui_shit, ctx, settings_vbox);
let new_ui_enabled_entry = gui_checkbox_setting!("New UI", new_ui_enabled, ctx, settings_vbox);
let scrollable = ScrolledWindow::builder()
.child(&settings_vbox)
@ -251,6 +256,8 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
konata_size_entry,
#[weak]
remove_gui_shit_entry,
#[weak]
new_ui_enabled_entry,
move |_| {
let config = Config {
host: host_entry.text().to_string(),
@ -315,6 +322,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
formatting_enabled: formatting_enabled_entry.is_active(),
commands_enabled: commands_enabled_entry.is_active(),
notifications_enabled: notifications_enabled_entry.is_active(),
new_ui_enabled: new_ui_enabled_entry.is_active(),
debug_logs: debug_logs_entry.is_active(),
proxy: {
let proxy = proxy_entry.text().to_string();
@ -371,6 +379,8 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
konata_size_entry,
#[weak]
remove_gui_shit_entry,
#[weak]
new_ui_enabled_entry,
move |_| {
let config = Config::default();
ctx.set_config(&config);
@ -391,6 +401,7 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
oof_update_time_entry.set_text(&config.oof_update_time.to_string());
konata_size_entry.set_text(&config.konata_size.to_string());
remove_gui_shit_entry.set_active(config.remove_gui_shit);
new_ui_enabled_entry.set_active(config.new_ui_enabled);
}
));
let window = Window::builder()
@ -779,6 +790,8 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
notifications: Arc::new(RwLock::new(Vec::<libnotify::Notification>::new())),
#[cfg(all(not(feature = "libnotify"), not(feature = "notify-rust")))]
notifications: Arc::new(RwLock::new(Vec::<String>::new())),
default_avatar: load_pixbuf(include_bytes!("images/avatar.png")).unwrap(),
avatars: Arc::new(RwLock::new(HashMap::new())),
}
}
@ -929,23 +942,13 @@ fn send_notification(_: Arc<Context>, ui: &UiModel, title: &str, message: &str)
ui.notifications.write().unwrap().push(id);
}
fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool) {
let notify = notify && ctx.config(|c| c.notifications_enabled);
let formatting_enabled = ctx.config(|c| c.formatting_enabled);
let Some(sanitized) = (if formatting_enabled {
sanitize_message(message.clone())
} else {
Some(message.clone())
}) else {
return;
};
if sanitized.is_empty() {
return;
}
fn get_message_box(
ctx: Arc<Context>,
ui: &UiModel,
message: String,
notify: bool,
formatting_enabled: bool,
) -> GtkBox {
// TODO: softcode these colors
let (ip_color, date_color, text_color) = if ui.is_dark_theme {
@ -956,7 +959,7 @@ fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool
let mut label = String::new();
if let (true, Some((date, ip, content, nick, avatar))) =
if let (true, Some((date, ip, content, nick, _))) =
(formatting_enabled, parse_message(message.clone()))
{
if let Some(ip) = ip {
@ -1031,7 +1034,133 @@ fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool
hbox.set_hexpand(true);
ui.chat_box.append(&hbox);
hbox
}
fn load_avatar(ui: &UiModel, url: &str) -> Option<Pixbuf> {
Some(ui.default_avatar.clone())
}
fn get_new_message_box(
ctx: Arc<Context>,
ui: &UiModel,
message: String,
notify: bool,
formatting_enabled: bool,
) -> GtkBox {
// TODO: softcode these colors
let (ip_color, date_color, text_color) = if ui.is_dark_theme {
("#494949", "#929292", "#FFFFFF")
} else {
("#585858", "#292929", "#000000")
};
let (date, ip, content, name, color, avatar) =
if let (true, Some((date, ip, content, nick, avatar))) =
(formatting_enabled, parse_message(message.clone()))
{
(
date,
ip,
content,
nick.as_ref()
.map(|o| o.0.to_string())
.unwrap_or("System".to_string()),
nick.as_ref()
.map(|o| o.1.to_string())
.unwrap_or("#DDDDDD".to_string()),
avatar
.and_then(|o| load_avatar(ui, &o))
.unwrap_or(ui.default_avatar.clone()),
)
} else {
(
Local::now().format("%d.%m.%Y %H:%M").to_string(),
None,
message,
"System".to_string(),
"#DDDDDD".to_string(),
ui.default_avatar.clone(),
)
};
let hbox = GtkBox::new(Orientation::Horizontal, 2);
let avatar_picture = Picture::for_pixbuf(&avatar);
avatar_picture.set_css_classes(&["message-avatar"]);
avatar_picture.set_size_request(32, 32);
avatar_picture.set_vexpand(false);
avatar_picture.set_hexpand(false);
avatar_picture.set_valign(Align::Start);
avatar_picture.set_halign(Align::Start);
hbox.append(&avatar_picture);
let vbox = GtkBox::new(Orientation::Vertical, 2);
vbox.append(&Label::builder()
.label(format!(
"<span color=\"{color}\">{}</span> <span color=\"{date_color}\">{}</span> <span color=\"{ip_color}\">{}</span>",
glib::markup_escape_text(&name),
glib::markup_escape_text(&date),
glib::markup_escape_text(&ip.unwrap_or_default()),
))
.halign(Align::Start)
.valign(Align::Start)
.selectable(true)
.wrap(true)
.wrap_mode(WrapMode::WordChar)
.use_markup(true)
.vexpand(true)
.build());
vbox.append(&Label::builder()
.label(format!(
"<span color=\"{text_color}\">{}</span>",
glib::markup_escape_text(&content)
))
.halign(Align::Start)
.valign(Align::Start)
.selectable(true)
.wrap(true)
.wrap_mode(WrapMode::WordChar)
.use_markup(true)
.vexpand(true)
.build());
vbox.set_valign(Align::Fill);
vbox.set_halign(Align::Fill);
vbox.set_vexpand(true);
vbox.set_hexpand(true);
hbox.append(&vbox);
hbox
}
fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool) {
let notify = notify && ctx.config(|c| c.notifications_enabled);
let formatting_enabled = ctx.config(|c| c.formatting_enabled);
let Some(sanitized) = (if formatting_enabled {
sanitize_message(message.clone())
} else {
Some(message.clone())
}) else {
return;
};
if sanitized.is_empty() {
return;
}
if ctx.config(|o| o.new_ui_enabled) {
ui.chat_box.append(&get_new_message_box(ctx.clone(), ui, message, notify, formatting_enabled));
} else {
ui.chat_box.append(&get_message_box(ctx.clone(), ui, message, notify, formatting_enabled));
}
timeout_add_local_once(Duration::from_millis(1000), move || {
GLOBAL.with(|global| {

BIN
src/chat/images/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -1,6 +1,6 @@
/* Now made with GTK Pango Markup */
/* .message-content { color:rgb(255, 255, 255); }
.message-content { color:rgb(255, 255, 255); }
.message-date { color:rgb(146, 146, 146); }
.message-ip { color:rgb(73, 73, 73); } */
.message-ip { color:rgb(73, 73, 73); }

View File

@ -1,6 +1,6 @@
/* Now made with GTK Pango Markup */
/* .message-content { color:rgb(0, 0, 0); }
.message-content { color:rgb(0, 0, 0); }
.message-date { color:rgb(41, 41, 41); }
.message-ip { color:rgb(88, 88, 88); } */
.message-ip { color:rgb(88, 88, 88); }

View File

@ -14,11 +14,15 @@
font-weight: bold;
}
.message-avatar {
border-radius: 64px;
}
/* Now made with GTK Pango Markup */
/* .message-name { font-weight: bold; }
.message-name { font-weight: bold; }
.message-name-green { color: #70fa7a; }
/* .message-name-green { color: #70fa7a; }
.message-name-red { color: #fa7070; }
.message-name-magenta { color: #da70fa; }
.message-name-cyan { color: #70fadc; } */