[desktop] Add rust-dark-light as direct dependency
This commit is contained in:
92
desktop/tauri/rust-dark-light/src/freedesktop.rs
Normal file
92
desktop/tauri/rust-dark-light/src/freedesktop.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use detect_desktop_environment::DesktopEnvironment;
|
||||
use ini::Ini;
|
||||
use std::path::{Path, PathBuf};
|
||||
use zbus::blocking::Connection;
|
||||
|
||||
use crate::Mode;
|
||||
|
||||
const XDG_KDEGLOBALS: &str = "/etc/xdg/kdeglobals";
|
||||
|
||||
fn get_freedesktop_color_scheme() -> Option<Mode> {
|
||||
let conn = Connection::session();
|
||||
if conn.is_err() {
|
||||
return None;
|
||||
}
|
||||
let reply = conn.unwrap().call_method(
|
||||
Some("org.freedesktop.portal.Desktop"),
|
||||
"/org/freedesktop/portal/desktop",
|
||||
Some("org.freedesktop.portal.Settings"),
|
||||
"Read",
|
||||
&("org.freedesktop.appearance", "color-scheme"),
|
||||
);
|
||||
if let Ok(reply) = &reply {
|
||||
let theme = reply.body().deserialize::<u32>();
|
||||
if theme.is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match theme.unwrap() {
|
||||
1 => Some(Mode::Dark),
|
||||
2 => Some(Mode::Light),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_gtk(pattern: &str) -> Mode {
|
||||
match dconf_rs::get_string(pattern) {
|
||||
Ok(theme) => Mode::from(theme.to_lowercase().contains("dark")),
|
||||
Err(_) => Mode::Light,
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_kde(path: &str) -> Mode {
|
||||
match Ini::load_from_file(path) {
|
||||
Ok(cfg) => {
|
||||
let section = match cfg.section(Some("Colors:Window")) {
|
||||
Some(section) => section,
|
||||
None => return Mode::Light,
|
||||
};
|
||||
let values = match section.get("BackgroundNormal") {
|
||||
Some(string) => string,
|
||||
None => return Mode::Light,
|
||||
};
|
||||
let rgb = values
|
||||
.split(',')
|
||||
.map(|s| s.parse::<u32>().unwrap_or(255))
|
||||
.collect::<Vec<u32>>();
|
||||
let rgb = if rgb.len() > 2 {
|
||||
rgb
|
||||
} else {
|
||||
vec![255, 255, 255]
|
||||
};
|
||||
let (r, g, b) = (rgb[0], rgb[1], rgb[2]);
|
||||
Mode::rgb(r, g, b)
|
||||
}
|
||||
Err(_) => Mode::Light,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect() -> Mode {
|
||||
match get_freedesktop_color_scheme() {
|
||||
Some(mode) => mode,
|
||||
// Other desktop environments are still being worked on, fow now, only the following implementations work.
|
||||
None => match DesktopEnvironment::detect() {
|
||||
DesktopEnvironment::Kde => {
|
||||
let path = if Path::new(XDG_KDEGLOBALS).exists() {
|
||||
PathBuf::from(XDG_KDEGLOBALS)
|
||||
} else {
|
||||
dirs::home_dir().unwrap().join(".config/kdeglobals")
|
||||
};
|
||||
detect_kde(path.to_str().unwrap())
|
||||
}
|
||||
DesktopEnvironment::Cinnamon => detect_gtk("/org/cinnamon/desktop/interface/gtk-theme"),
|
||||
DesktopEnvironment::Gnome => detect_gtk("/org/gnome/desktop/interface/gtk-theme"),
|
||||
DesktopEnvironment::Mate => detect_gtk("/org/mate/desktop/interface/gtk-theme"),
|
||||
DesktopEnvironment::Unity => detect_gtk("/org/gnome/desktop/interface/gtk-theme"),
|
||||
_ => Mode::Default,
|
||||
},
|
||||
}
|
||||
}
|
||||
73
desktop/tauri/rust-dark-light/src/lib.rs
Normal file
73
desktop/tauri/rust-dark-light/src/lib.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
//! Detect if dark mode or light mode is enabled.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! let mode = dark_light::detect();
|
||||
//!
|
||||
//! match mode {
|
||||
//! // Dark mode
|
||||
//! dark_light::Mode::Dark => {},
|
||||
//! // Light mode
|
||||
//! dark_light::Mode::Light => {},
|
||||
//! // Unspecified
|
||||
//! dark_light::Mode::Default => {},
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
mod platforms;
|
||||
use platforms::platform;
|
||||
|
||||
mod utils;
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
use utils::rgb::Rgb;
|
||||
|
||||
/// Enum representing dark mode, light mode, or unspecified.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Mode {
|
||||
/// Dark mode
|
||||
Dark,
|
||||
/// Light mode
|
||||
Light,
|
||||
/// Unspecified
|
||||
Default,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
#[allow(dead_code)]
|
||||
fn from_bool(b: bool) -> Self {
|
||||
if b {
|
||||
Mode::Dark
|
||||
} else {
|
||||
Mode::Light
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
/// Convert an RGB color to [`Mode`]. The color is converted to grayscale, and if the grayscale value is less than 192, [`Mode::Dark`] is returned. Otherwise, [`Mode::Light`] is returned.
|
||||
fn from_rgb(rgb: Rgb) -> Self {
|
||||
let window_background_gray = (rgb.0 * 11 + rgb.1 * 16 + rgb.2 * 5) / 32;
|
||||
if window_background_gray < 192 {
|
||||
Self::Dark
|
||||
} else {
|
||||
Self::Light
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect if light mode or dark mode is enabled. If the mode can’t be detected, fall back to [`Mode::Default`].
|
||||
pub use platform::detect::detect;
|
||||
/// Notifies the user if the system theme has been changed.
|
||||
pub use platform::notify::subscribe;
|
||||
@@ -0,0 +1,47 @@
|
||||
use detect_desktop_environment::DesktopEnvironment;
|
||||
|
||||
use crate::Mode;
|
||||
|
||||
use super::{dconf_detect, gsetting_detect, kde_detect, CINNAMON, GNOME, MATE};
|
||||
|
||||
pub fn detect() -> Mode {
|
||||
NonFreeDesktop::detect()
|
||||
}
|
||||
|
||||
/// Detects the color scheme on a platform.
|
||||
trait ColorScheme {
|
||||
fn detect() -> Mode;
|
||||
}
|
||||
|
||||
/// Represents the FreeDesktop platform.
|
||||
struct FreeDesktop;
|
||||
|
||||
/// Represents non FreeDesktop platforms.
|
||||
struct NonFreeDesktop;
|
||||
|
||||
/// Detects the color scheme on FreeDesktop platforms. It makes use of the DBus interface.
|
||||
impl ColorScheme for FreeDesktop {
|
||||
fn detect() -> Mode {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// Detects the color scheme on non FreeDesktop platforms, having a custom implementation for each desktop environment.
|
||||
impl ColorScheme for NonFreeDesktop {
|
||||
fn detect() -> Mode {
|
||||
match DesktopEnvironment::detect() {
|
||||
Some(mode) => match mode {
|
||||
DesktopEnvironment::Kde => match kde_detect() {
|
||||
Ok(mode) => mode,
|
||||
Err(_) => Mode::Default,
|
||||
},
|
||||
DesktopEnvironment::Cinnamon => dconf_detect(CINNAMON),
|
||||
DesktopEnvironment::Gnome => gsetting_detect(),
|
||||
DesktopEnvironment::Mate => dconf_detect(MATE),
|
||||
DesktopEnvironment::Unity => dconf_detect(GNOME),
|
||||
_ => Mode::Default,
|
||||
},
|
||||
None => Mode::Default,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use std::{process::Command, str::FromStr};
|
||||
|
||||
use anyhow::Context;
|
||||
use ini::Ini;
|
||||
|
||||
use crate::{utils::rgb::Rgb, Mode};
|
||||
|
||||
pub mod detect;
|
||||
pub mod notify;
|
||||
|
||||
const MATE: &str = "/org/mate/desktop/interface/gtk-theme";
|
||||
const GNOME: &str = "/org/gnome/desktop/interface/gtk-theme";
|
||||
const CINNAMON: &str = "/org/cinnamon/desktop/interface/gtk-theme";
|
||||
|
||||
fn dconf_detect(path: &str) -> Mode {
|
||||
match dconf_rs::get_string(path) {
|
||||
Ok(theme) => {
|
||||
println!("dconf output: {}", theme);
|
||||
if theme.is_empty() {
|
||||
Mode::Default
|
||||
} else {
|
||||
if theme.to_lowercase().contains("dark") {
|
||||
Mode::Dark
|
||||
} else {
|
||||
Mode::Light
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => Mode::Default,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gsetting_detect() -> Mode {
|
||||
let mode = match Command::new("gsettings")
|
||||
.arg("get")
|
||||
.arg("org.gnome.desktop.interface")
|
||||
.arg("color-scheme")
|
||||
.output()
|
||||
{
|
||||
Ok(output) => {
|
||||
if let Ok(scheme) = String::from_utf8(output.stdout) {
|
||||
if scheme.contains("prefer-dark") {
|
||||
Mode::Dark
|
||||
} else if scheme.contains("prefer-light") {
|
||||
Mode::Dark
|
||||
} else {
|
||||
Mode::Default
|
||||
}
|
||||
} else {
|
||||
Mode::Default
|
||||
}
|
||||
}
|
||||
Err(_) => Mode::Default,
|
||||
};
|
||||
|
||||
// Fallback to dconf
|
||||
if mode == Mode::Default {
|
||||
return dconf_detect(GNOME);
|
||||
}
|
||||
|
||||
mode
|
||||
}
|
||||
|
||||
fn kde_detect() -> anyhow::Result<Mode> {
|
||||
let xdg = xdg::BaseDirectories::new()?;
|
||||
let path = xdg
|
||||
.find_config_file("kdeglobals")
|
||||
.context("Path not found")?;
|
||||
let cfg = Ini::load_from_file(path)?;
|
||||
let properties = cfg
|
||||
.section(Some("Colors:Window"))
|
||||
.context("Failed to get section Colors:Window")?;
|
||||
let background = properties
|
||||
.get("BackgroundNormal")
|
||||
.context("Failed to get BackgroundNormal inside Colors:Window")?;
|
||||
let rgb = Rgb::from_str(background).unwrap();
|
||||
Ok(Mode::from_rgb(rgb))
|
||||
}
|
||||
|
||||
impl From<ashpd::desktop::settings::ColorScheme> for Mode {
|
||||
fn from(value: ashpd::desktop::settings::ColorScheme) -> Self {
|
||||
match value {
|
||||
ashpd::desktop::settings::ColorScheme::NoPreference => Mode::Default,
|
||||
ashpd::desktop::settings::ColorScheme::PreferDark => Mode::Dark,
|
||||
ashpd::desktop::settings::ColorScheme::PreferLight => Mode::Light,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use ashpd::desktop::settings::{ColorScheme, Settings};
|
||||
use futures::{stream, Stream, StreamExt};
|
||||
use std::task::Poll;
|
||||
|
||||
use crate::{detect, Mode};
|
||||
|
||||
pub async fn subscribe() -> anyhow::Result<impl Stream<Item = Mode> + Send> {
|
||||
let stream = if get_freedesktop_color_scheme().await.is_ok() {
|
||||
let proxy = Settings::new().await?;
|
||||
proxy
|
||||
.receive_color_scheme_changed()
|
||||
.await?
|
||||
.map(Mode::from)
|
||||
.boxed()
|
||||
} else {
|
||||
let mut last_mode = detect();
|
||||
stream::poll_fn(move |ctx| -> Poll<Option<Mode>> {
|
||||
let current_mode = detect();
|
||||
if current_mode != last_mode {
|
||||
last_mode = current_mode;
|
||||
Poll::Ready(Some(current_mode))
|
||||
} else {
|
||||
ctx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
};
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
async fn get_freedesktop_color_scheme() -> anyhow::Result<Mode> {
|
||||
let proxy = Settings::new().await?;
|
||||
let color_scheme = proxy.color_scheme().await?;
|
||||
let mode = match color_scheme {
|
||||
ColorScheme::PreferDark => Mode::Dark,
|
||||
ColorScheme::PreferLight => Mode::Light,
|
||||
ColorScheme::NoPreference => Mode::Default,
|
||||
};
|
||||
Ok(mode)
|
||||
}
|
||||
56
desktop/tauri/rust-dark-light/src/platforms/macos/detect.rs
Normal file
56
desktop/tauri/rust-dark-light/src/platforms/macos/detect.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
// Dark/light mode detection on macOS.
|
||||
// Written with help from Ryan McGrath (https://rymc.io/).
|
||||
|
||||
use crate::Mode;
|
||||
use objc::runtime::Object;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
extern "C" {
|
||||
static NSAppearanceNameAqua: *const Object;
|
||||
static NSAppearanceNameAccessibilityHighContrastAqua: *const Object;
|
||||
static NSAppearanceNameDarkAqua: *const Object;
|
||||
static NSAppearanceNameAccessibilityHighContrastDarkAqua: *const Object;
|
||||
}
|
||||
|
||||
fn is_dark_mode_enabled() -> bool {
|
||||
unsafe {
|
||||
let mut appearance: *const Object = msg_send![class!(NSAppearance), currentAppearance];
|
||||
if appearance.is_null() {
|
||||
appearance = msg_send![class!(NSApp), effectiveAppearance];
|
||||
}
|
||||
|
||||
let objects = [
|
||||
NSAppearanceNameAqua,
|
||||
NSAppearanceNameAccessibilityHighContrastAqua,
|
||||
NSAppearanceNameDarkAqua,
|
||||
NSAppearanceNameAccessibilityHighContrastDarkAqua,
|
||||
];
|
||||
let names: *const Object = msg_send![
|
||||
class!(NSArray),
|
||||
arrayWithObjects:objects.as_ptr()
|
||||
count:objects.len()
|
||||
];
|
||||
|
||||
// `bestMatchFromAppearancesWithNames` is only available in macOS 10.14+.
|
||||
// Gracefully handle earlier versions.
|
||||
let responds_to_selector: objc::runtime::BOOL = msg_send![
|
||||
appearance,
|
||||
respondsToSelector: sel!(bestMatchFromAppearancesWithNames:)
|
||||
];
|
||||
if responds_to_selector == objc::runtime::NO {
|
||||
return false;
|
||||
}
|
||||
|
||||
let style: *const Object = msg_send![
|
||||
appearance,
|
||||
bestMatchFromAppearancesWithNames:&*names
|
||||
];
|
||||
|
||||
style == NSAppearanceNameDarkAqua
|
||||
|| style == NSAppearanceNameAccessibilityHighContrastDarkAqua
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect() -> crate::Mode {
|
||||
Mode::from_bool(is_dark_mode_enabled())
|
||||
}
|
||||
2
desktop/tauri/rust-dark-light/src/platforms/macos/mod.rs
Normal file
2
desktop/tauri/rust-dark-light/src/platforms/macos/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod detect;
|
||||
pub mod notify;
|
||||
23
desktop/tauri/rust-dark-light/src/platforms/macos/notify.rs
Normal file
23
desktop/tauri/rust-dark-light/src/platforms/macos/notify.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::task::Poll;
|
||||
|
||||
use futures::{stream, Stream};
|
||||
|
||||
use crate::{detect, Mode};
|
||||
|
||||
pub async fn subscribe() -> anyhow::Result<impl Stream<Item = Mode> + Send> {
|
||||
let mut last_mode = detect();
|
||||
|
||||
let stream = stream::poll_fn(move |ctx| -> Poll<Option<Mode>> {
|
||||
let current_mode = detect();
|
||||
|
||||
if current_mode != last_mode {
|
||||
last_mode = current_mode;
|
||||
Poll::Ready(Some(current_mode))
|
||||
} else {
|
||||
ctx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
});
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
48
desktop/tauri/rust-dark-light/src/platforms/mod.rs
Normal file
48
desktop/tauri/rust-dark-light/src/platforms/mod.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use macos as platform;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows as platform;
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
pub mod freedesktop;
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
pub use freedesktop as platform;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod websys;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use websys as platform;
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_arch = "wasm32"
|
||||
)))]
|
||||
pub mod platform {
|
||||
pub fn detect() -> crate::Mode {
|
||||
super::Mode::Light
|
||||
}
|
||||
}
|
||||
11
desktop/tauri/rust-dark-light/src/platforms/websys/detect.rs
Normal file
11
desktop/tauri/rust-dark-light/src/platforms/websys/detect.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::Mode;
|
||||
|
||||
pub fn detect() -> crate::Mode {
|
||||
if let Some(window) = web_sys::window() {
|
||||
let query_result = window.match_media("(prefers-color-scheme: dark)");
|
||||
if let Ok(Some(mql)) = query_result {
|
||||
return Mode::from_bool(mql.matches());
|
||||
}
|
||||
}
|
||||
Mode::Light
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod detect;
|
||||
pub mod notify;
|
||||
23
desktop/tauri/rust-dark-light/src/platforms/websys/notify.rs
Normal file
23
desktop/tauri/rust-dark-light/src/platforms/websys/notify.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::task::Poll;
|
||||
|
||||
use futures::{stream, Stream};
|
||||
|
||||
use crate::{detect, Mode};
|
||||
|
||||
pub async fn subscribe() -> anyhow::Result<impl Stream<Item = Mode> + Send> {
|
||||
let mut last_mode = detect();
|
||||
|
||||
let stream = stream::poll_fn(move |ctx| -> Poll<Option<Mode>> {
|
||||
let current_mode = detect();
|
||||
|
||||
if current_mode != last_mode {
|
||||
last_mode = current_mode;
|
||||
Poll::Ready(Some(current_mode))
|
||||
} else {
|
||||
ctx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
});
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
use crate::Mode;
|
||||
use winreg::RegKey;
|
||||
|
||||
const SUBKEY: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
|
||||
const VALUE: &str = "AppsUseLightTheme";
|
||||
|
||||
pub fn detect() -> Mode {
|
||||
let hkcu = RegKey::predef(winreg::enums::HKEY_CURRENT_USER);
|
||||
if let Ok(subkey) = hkcu.open_subkey(SUBKEY) {
|
||||
if let Ok(dword) = subkey.get_value::<u32, _>(VALUE) {
|
||||
return Mode::from_bool(dword == 0);
|
||||
}
|
||||
}
|
||||
Mode::Light
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod detect;
|
||||
pub mod notify;
|
||||
@@ -0,0 +1,23 @@
|
||||
use std::task::Poll;
|
||||
|
||||
use futures::{stream, Stream};
|
||||
|
||||
use crate::{detect, Mode};
|
||||
|
||||
pub async fn subscribe() -> anyhow::Result<impl Stream<Item = Mode> + Send> {
|
||||
let mut last_mode = detect();
|
||||
|
||||
let stream = stream::poll_fn(move |ctx| -> Poll<Option<Mode>> {
|
||||
let current_mode = detect();
|
||||
|
||||
if current_mode != last_mode {
|
||||
last_mode = current_mode;
|
||||
Poll::Ready(Some(current_mode))
|
||||
} else {
|
||||
ctx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
});
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
1
desktop/tauri/rust-dark-light/src/utils/mod.rs
Normal file
1
desktop/tauri/rust-dark-light/src/utils/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod rgb;
|
||||
23
desktop/tauri/rust-dark-light/src/utils/rgb.rs
Normal file
23
desktop/tauri/rust-dark-light/src/utils/rgb.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Struct representing an RGB color
|
||||
pub(crate) struct Rgb(pub(crate) u32, pub(crate) u32, pub(crate) u32);
|
||||
|
||||
impl FromStr for Rgb {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let rgb = s
|
||||
.split(',')
|
||||
.map(|s| s.parse::<u32>().unwrap_or(255))
|
||||
.try_fold(vec![], |mut acc, x| {
|
||||
if acc.len() < 3 {
|
||||
acc.push(x);
|
||||
Ok(acc)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("RGB format is invalid"))
|
||||
}
|
||||
})?;
|
||||
Ok(Rgb(rgb[0], rgb[1], rgb[2]))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user