mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 03:03:46 +08:00
feat: macos display colorful icon with speed rate (#2771)
This commit is contained in:
parent
1258e187f5
commit
76be5d8469
BIN
src-tauri/assets/fonts/SF-Pro.ttf
Executable file
BIN
src-tauri/assets/fonts/SF-Pro.ttf
Executable file
Binary file not shown.
@ -225,23 +225,27 @@ impl Tray {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
let enable_tray_speed = Config::verge().latest().enable_tray_speed.unwrap_or(true);
|
let enable_tray_speed = Config::verge().latest().enable_tray_speed.unwrap_or(true);
|
||||||
let is_template =
|
let is_colorful = tray_icon == "colorful";
|
||||||
crate::utils::help::is_monochrome_image_from_bytes(&icon_bytes).unwrap_or(false);
|
|
||||||
|
|
||||||
let icon_bytes = if enable_tray_speed {
|
// 处理图标和速率
|
||||||
|
let final_icon_bytes = if enable_tray_speed {
|
||||||
let rate = rate.or_else(|| {
|
let rate = rate.or_else(|| {
|
||||||
self.speed_rate
|
self.speed_rate
|
||||||
.lock()
|
.lock()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|speed_rate| speed_rate.get_curent_rate())
|
.and_then(|speed_rate| speed_rate.get_curent_rate())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 使用新的方法渲染图标和速率
|
||||||
SpeedRate::add_speed_text(icon_bytes, rate)?
|
SpeedRate::add_speed_text(icon_bytes, rate)?
|
||||||
} else {
|
} else {
|
||||||
icon_bytes
|
icon_bytes
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
|
// 设置系统托盘图标
|
||||||
let _ = tray.set_icon_as_template(is_template);
|
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&final_icon_bytes)?));
|
||||||
|
// 只对单色图标使用 template 模式
|
||||||
|
let _ = tray.set_icon_as_template(!is_colorful);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
@ -2,7 +2,7 @@ use crate::core::clash_api::{get_traffic_ws_url, Rate};
|
|||||||
use crate::utils::help::format_bytes_speed;
|
use crate::utils::help::format_bytes_speed;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use image::{ImageBuffer, Rgba};
|
use image::{Rgba, GenericImageView, RgbaImage};
|
||||||
use imageproc::drawing::draw_text_mut;
|
use imageproc::drawing::draw_text_mut;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rusttype::{Font, Scale};
|
use rusttype::{Font, Scale};
|
||||||
@ -14,7 +14,7 @@ use tokio_tungstenite::tungstenite::Message;
|
|||||||
pub struct SpeedRate {
|
pub struct SpeedRate {
|
||||||
rate: Arc<Mutex<(Rate, Rate)>>,
|
rate: Arc<Mutex<(Rate, Rate)>>,
|
||||||
last_update: Arc<Mutex<std::time::Instant>>,
|
last_update: Arc<Mutex<std::time::Instant>>,
|
||||||
base_image: Arc<Mutex<Option<(ImageBuffer<Rgba<u8>, Vec<u8>>, u32, u32)>>>, // 存储基础图像和尺寸
|
// 移除 base_image,不再缓存原始图像
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpeedRate {
|
impl SpeedRate {
|
||||||
@ -22,7 +22,6 @@ impl SpeedRate {
|
|||||||
Self {
|
Self {
|
||||||
rate: Arc::new(Mutex::new((Rate::default(), Rate::default()))),
|
rate: Arc::new(Mutex::new((Rate::default(), Rate::default()))),
|
||||||
last_update: Arc::new(Mutex::new(std::time::Instant::now())),
|
last_update: Arc::new(Mutex::new(std::time::Instant::now())),
|
||||||
base_image: Arc::new(Mutex::new(None)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,125 +66,107 @@ impl SpeedRate {
|
|||||||
Some(current.clone())
|
Some(current.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_speed_text(icon: Vec<u8>, rate: Option<Rate>) -> Result<Vec<u8>> {
|
// 分离图标加载和速率渲染
|
||||||
|
pub fn add_speed_text(icon_bytes: Vec<u8>, rate: Option<Rate>) -> Result<Vec<u8>> {
|
||||||
let rate = rate.unwrap_or(Rate { up: 0, down: 0 });
|
let rate = rate.unwrap_or(Rate { up: 0, down: 0 });
|
||||||
|
|
||||||
// 获取或创建基础图像
|
// 加载原始图标
|
||||||
let base_image = {
|
let icon_image = image::load_from_memory(&icon_bytes)?;
|
||||||
let tray = Self::global();
|
let (icon_width, icon_height) = (icon_image.width(), icon_image.height());
|
||||||
let mut base = tray.base_image.lock();
|
|
||||||
if base.is_none() {
|
|
||||||
let img = image::load_from_memory(&icon)?;
|
|
||||||
let (width, height) = (img.width(), img.height());
|
|
||||||
let icon_text_gap = 10;
|
|
||||||
let max_text_width = 510.0;
|
|
||||||
let total_width = width as f32 + icon_text_gap as f32 + max_text_width;
|
|
||||||
|
|
||||||
let mut image = ImageBuffer::new(total_width.ceil() as u32, height);
|
// 判断是否为彩色图标
|
||||||
image::imageops::replace(&mut image, &img, 0_i64, 0_i64);
|
let is_colorful = !crate::utils::help::is_monochrome_image_from_bytes(&icon_bytes).unwrap_or(false);
|
||||||
*base = Some((image, width, height));
|
|
||||||
}
|
|
||||||
base.clone().unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
let (mut image, width, height) = base_image;
|
// 增加文本宽度和间距
|
||||||
|
let icon_text_gap = 80; // 增加图标和文本之间的间距
|
||||||
|
let text_width = 520; // 增加文本区域宽度,确保能显示完整
|
||||||
|
let total_width = icon_width + icon_text_gap + text_width;
|
||||||
|
|
||||||
let font =
|
// 创建新的透明画布
|
||||||
Font::try_from_bytes(include_bytes!("../../../assets/fonts/FiraCode-Medium.ttf")).unwrap();
|
let mut combined_image = RgbaImage::new(total_width, icon_height);
|
||||||
|
|
||||||
// 修改颜色和阴影参数
|
// 将原始图标绘制到新画布的左侧
|
||||||
let text_color = Rgba([255u8, 255u8, 255u8, 255u8]); // 纯白色
|
for y in 0..icon_height {
|
||||||
let shadow_color = Rgba([0u8, 0u8, 0u8, 120u8]); // 降低阴影不透明度
|
for x in 0..icon_width {
|
||||||
let base_size = height as f32 * 0.6; // 保持字体大小
|
let pixel = icon_image.get_pixel(x, y);
|
||||||
let scale = Scale::uniform(base_size);
|
combined_image.put_pixel(x, y, pixel);
|
||||||
|
|
||||||
let up_text = format_bytes_speed(rate.up);
|
|
||||||
let down_text = format_bytes_speed(rate.down);
|
|
||||||
|
|
||||||
// 计算文本宽度
|
|
||||||
let up_width = font
|
|
||||||
.layout(&up_text, scale, rusttype::Point { x: 0.0, y: 0.0 })
|
|
||||||
.map(|g| g.position().x + g.unpositioned().h_metrics().advance_width)
|
|
||||||
.last()
|
|
||||||
.unwrap_or(0.0);
|
|
||||||
|
|
||||||
let down_width = font
|
|
||||||
.layout(&down_text, scale, rusttype::Point { x: 0.0, y: 0.0 })
|
|
||||||
.map(|g| g.position().x + g.unpositioned().h_metrics().advance_width)
|
|
||||||
.last()
|
|
||||||
.unwrap_or(0.0);
|
|
||||||
|
|
||||||
let icon_text_gap = 10;
|
|
||||||
let max_text_width: f32 = 510.0;
|
|
||||||
let text_area_start = width as i32 + icon_text_gap;
|
|
||||||
|
|
||||||
// 用透明色清除文字区域
|
|
||||||
for x in text_area_start..image.width() as i32 {
|
|
||||||
for y in 0..image.height() as i32 {
|
|
||||||
image.put_pixel(x as u32, y as u32, Rgba([0, 0, 0, 0]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算文字的起始x坐标,使文字右对齐
|
// 选择文本颜色
|
||||||
let text_start_x_up = (width as f32 + icon_text_gap as f32 + max_text_width - up_width).max(width as f32 + icon_text_gap as f32) as i32;
|
let (text_color, shadow_color) = if is_colorful {
|
||||||
let text_start_x_down = (width as f32 + icon_text_gap as f32 + max_text_width - down_width).max(width as f32 + icon_text_gap as f32) as i32;
|
// 彩色图标使用黑色文本和轻微白色阴影
|
||||||
|
(Rgba([255u8, 255u8, 255u8, 255u8]), Rgba([0u8, 0u8, 0u8, 160u8]))
|
||||||
|
} else {
|
||||||
|
// 单色图标使用白色文本和轻微黑色阴影
|
||||||
|
(Rgba([255u8, 255u8, 255u8, 255u8]), Rgba([0u8, 0u8, 0u8, 120u8]))
|
||||||
|
};
|
||||||
|
|
||||||
// 计算垂直位置
|
// 减小字体大小以适应文本区域
|
||||||
let up_y = 0; // 上行速率紧贴顶部
|
let font = Font::try_from_bytes(include_bytes!("../../../assets/fonts/SF-Pro.ttf")).unwrap();
|
||||||
let down_y = height as i32 - base_size as i32; // 下行速率紧贴底部
|
let font_size = icon_height as f32 * 0.6; // 稍微减小字体
|
||||||
|
let scale = Scale::uniform(font_size);
|
||||||
|
|
||||||
|
// 使用更简洁的速率格式
|
||||||
|
let up_text = format_bytes_speed(rate.up);
|
||||||
|
let down_text = format_bytes_speed(rate.down);
|
||||||
|
|
||||||
|
// 计算文本位置,确保垂直间距合适
|
||||||
|
let text_x = icon_width + icon_text_gap;
|
||||||
|
let up_y = 0;
|
||||||
|
let down_y_offset = 32 as i32;
|
||||||
|
let down_y = icon_height as i32 - font_size as i32 + down_y_offset; // 下行速率显示在下方
|
||||||
|
|
||||||
|
|
||||||
|
// 绘制速率文本(先阴影后文字)
|
||||||
let shadow_offset = 1;
|
let shadow_offset = 1;
|
||||||
|
|
||||||
// 绘制上行速率(先画阴影,再画文字)
|
// 绘制上行速率
|
||||||
draw_text_mut(
|
draw_text_mut(
|
||||||
&mut image,
|
&mut combined_image,
|
||||||
shadow_color,
|
shadow_color,
|
||||||
text_start_x_up + shadow_offset,
|
text_x as i32 + shadow_offset,
|
||||||
up_y + shadow_offset,
|
up_y + shadow_offset,
|
||||||
scale,
|
scale,
|
||||||
&font,
|
&font,
|
||||||
&up_text,
|
&up_text,
|
||||||
);
|
);
|
||||||
draw_text_mut(
|
draw_text_mut(
|
||||||
&mut image,
|
&mut combined_image,
|
||||||
text_color,
|
text_color,
|
||||||
text_start_x_up,
|
text_x as i32,
|
||||||
up_y,
|
up_y,
|
||||||
scale,
|
scale,
|
||||||
&font,
|
&font,
|
||||||
&up_text,
|
&up_text,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 绘制下行速率(先画阴影,再画文字)
|
// 绘制下行速率
|
||||||
draw_text_mut(
|
draw_text_mut(
|
||||||
&mut image,
|
&mut combined_image,
|
||||||
shadow_color,
|
shadow_color,
|
||||||
text_start_x_down + shadow_offset,
|
text_x as i32 + shadow_offset,
|
||||||
down_y + shadow_offset,
|
down_y + shadow_offset,
|
||||||
scale,
|
scale,
|
||||||
&font,
|
&font,
|
||||||
&down_text,
|
&down_text,
|
||||||
);
|
);
|
||||||
draw_text_mut(
|
draw_text_mut(
|
||||||
&mut image,
|
&mut combined_image,
|
||||||
text_color,
|
text_color,
|
||||||
text_start_x_down,
|
text_x as i32,
|
||||||
down_y,
|
down_y,
|
||||||
scale,
|
scale,
|
||||||
&font,
|
&font,
|
||||||
&down_text,
|
&down_text,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut bytes: Vec<u8> = Vec::new();
|
// 将结果转换为 PNG 数据
|
||||||
let mut cursor = Cursor::new(&mut bytes);
|
let mut bytes = Vec::new();
|
||||||
image.write_to(&mut cursor, image::ImageFormat::Png)?;
|
combined_image.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)?;
|
||||||
Ok(bytes)
|
Ok(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn global() -> &'static SpeedRate {
|
|
||||||
static INSTANCE: once_cell::sync::OnceCell<SpeedRate> = once_cell::sync::OnceCell::new();
|
|
||||||
INSTANCE.get_or_init(SpeedRate::new)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user