first pass of merging in warp (doesn't build)
This commit is contained in:
+199
-28
@@ -1,24 +1,22 @@
|
||||
use super::default_themes::*;
|
||||
use anyhow::Result;
|
||||
use galaxy_core::ui::color::pick_foreground_color;
|
||||
use galaxyui::assets::asset_cache::AssetSource;
|
||||
use galaxyui::{
|
||||
color::ColorU,
|
||||
elements::{
|
||||
Align, Border, ConstrainedBox, Container, Element, Empty, Flex, ParentElement, Rect,
|
||||
Shrinkable, Stack, Text,
|
||||
},
|
||||
fonts::FamilyId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::iter::FromIterator;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::theme_creator::{pick_accent_color_from_options, top_colors_for_image};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
pub use galaxy_core::ui::color::blend::Blend;
|
||||
use galaxy_core::ui::color::pick_foreground_color;
|
||||
pub use galaxy_core::ui::theme::*;
|
||||
use galaxyui::assets::asset_cache::AssetSource;
|
||||
use galaxyui::color::ColorU;
|
||||
use galaxyui::elements::{
|
||||
Align, Border, ConstrainedBox, Container, Element, Empty, Flex, ParentElement, Rect,
|
||||
Shrinkable, Stack, Text,
|
||||
};
|
||||
use galaxyui::fonts::FamilyId;
|
||||
|
||||
use super::default_themes::*;
|
||||
use super::theme_creator::{pick_accent_color_from_options, top_colors_for_image};
|
||||
|
||||
const THUMBNAIL_MARGIN: f32 = 10.;
|
||||
|
||||
@@ -154,29 +152,201 @@ impl ThemeKind {
|
||||
let theme_name = format!("{self}").to_lowercase();
|
||||
theme_name.contains(&query.to_lowercase())
|
||||
}
|
||||
|
||||
pub(crate) fn is_custom_theme_reference_syncable(&self) -> bool {
|
||||
match self {
|
||||
ThemeKind::Custom(custom_theme) | ThemeKind::CustomBase16(custom_theme) => {
|
||||
custom_theme_path_is_portable(&custom_theme.path, &crate::user_config::themes_dir())
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
schemars::JsonSchema,
|
||||
settings_value::SettingsValue,
|
||||
Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, schemars::JsonSchema,
|
||||
)]
|
||||
#[schemars(description = "A user-provided custom theme.")]
|
||||
pub struct CustomTheme {
|
||||
#[schemars(description = "The display name of the custom theme.")]
|
||||
name: String,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_custom_theme_path",
|
||||
serialize_with = "serialize_custom_theme_path"
|
||||
)]
|
||||
#[schemars(description = "The file path to the custom theme definition.")]
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl settings_value::SettingsValue for CustomTheme {
|
||||
fn to_file_value(&self) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"name": &self.name,
|
||||
"path": custom_theme_path_storage_value(&self.path, &crate::user_config::themes_dir()),
|
||||
})
|
||||
}
|
||||
|
||||
fn from_file_value(value: &serde_json::Value) -> Option<Self> {
|
||||
#[derive(Deserialize)]
|
||||
struct FileValue {
|
||||
name: String,
|
||||
path: String,
|
||||
}
|
||||
|
||||
let value = serde_json::from_value::<FileValue>(value.clone()).ok()?;
|
||||
Some(Self {
|
||||
name: value.name,
|
||||
path: portable_custom_theme_path_from_stored_raw(
|
||||
&value.path,
|
||||
&crate::user_config::themes_dir(),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_custom_theme_path<S>(path: &Path, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if let Some(path) =
|
||||
portable_custom_theme_storage_string(path, &crate::user_config::themes_dir())
|
||||
{
|
||||
path.serialize(serializer)
|
||||
} else {
|
||||
path.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_custom_theme_path<'de, D>(deserializer: D) -> Result<PathBuf, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let path = String::deserialize(deserializer)?;
|
||||
Ok(portable_custom_theme_path_from_stored_raw(
|
||||
&path,
|
||||
&crate::user_config::themes_dir(),
|
||||
))
|
||||
}
|
||||
|
||||
fn custom_theme_path_storage_value(path: &Path, theme_root: &Path) -> serde_json::Value {
|
||||
if let Some(path) = portable_custom_theme_storage_string(path, theme_root) {
|
||||
serde_json::Value::String(path)
|
||||
} else {
|
||||
serde_json::json!(path)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn custom_theme_path_is_portable(path: &Path, theme_root: &Path) -> bool {
|
||||
if path_is_absolute_or_foreign_absolute(path) {
|
||||
return portable_custom_theme_storage_string(path, theme_root).is_some();
|
||||
}
|
||||
|
||||
path.to_str()
|
||||
.is_some_and(|path| portable_stored_raw_components(path).is_some())
|
||||
}
|
||||
|
||||
pub(crate) fn portable_custom_theme_path_from_stored_raw(raw: &str, theme_root: &Path) -> PathBuf {
|
||||
portable_stored_raw_components(raw)
|
||||
.map(|components| {
|
||||
components
|
||||
.iter()
|
||||
.fold(theme_root.to_path_buf(), |path, component| {
|
||||
path.join(component)
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| PathBuf::from(raw))
|
||||
}
|
||||
|
||||
pub(crate) fn portable_custom_theme_storage_string(
|
||||
path: &Path,
|
||||
theme_root: &Path,
|
||||
) -> Option<String> {
|
||||
if path_starts_with_windows_drive_prefix_using_forward_slash(path) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let relative = path.strip_prefix(theme_root).ok()?;
|
||||
let mut components = Vec::new();
|
||||
|
||||
for component in relative.components() {
|
||||
let Component::Normal(value) = component else {
|
||||
return None;
|
||||
};
|
||||
let value = value.to_str()?;
|
||||
if value.contains('\\') {
|
||||
return None;
|
||||
}
|
||||
components.push(value);
|
||||
}
|
||||
|
||||
if components.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let path = components.join("/");
|
||||
if portable_stored_raw_components(&path).is_some() {
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn portable_stored_raw_components(raw: &str) -> Option<Vec<&str>> {
|
||||
if raw.is_empty()
|
||||
|| raw.contains('\\')
|
||||
|| raw.starts_with('/')
|
||||
|| raw_starts_with_windows_drive_prefix(raw)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let components = raw.split('/').collect::<Vec<_>>();
|
||||
if components
|
||||
.iter()
|
||||
.all(|component| !component.is_empty() && *component != "." && *component != "..")
|
||||
{
|
||||
Some(components)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_starts_with_windows_drive_prefix(raw: &str) -> bool {
|
||||
let bytes = raw.as_bytes();
|
||||
bytes.len() >= 2 && bytes[0].is_ascii_alphabetic() && bytes[1] == b':'
|
||||
}
|
||||
|
||||
fn path_starts_with_windows_drive_prefix_using_forward_slash(path: &Path) -> bool {
|
||||
let Some(path) = path.as_os_str().to_str() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let bytes = path.as_bytes();
|
||||
bytes.len() >= 3 && bytes[0].is_ascii_alphabetic() && bytes[1] == b':' && bytes[2] == b'/'
|
||||
}
|
||||
|
||||
fn path_is_absolute_or_foreign_absolute(path: &Path) -> bool {
|
||||
path.has_root() || path_looks_like_foreign_windows_absolute(path)
|
||||
}
|
||||
|
||||
fn path_looks_like_foreign_windows_absolute(path: &Path) -> bool {
|
||||
if path.has_root() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(path) = path.as_os_str().to_str() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let bytes = path.as_bytes();
|
||||
let starts_with_drive_root = bytes.len() >= 3
|
||||
&& bytes[0].is_ascii_alphabetic()
|
||||
&& bytes[1] == b':'
|
||||
&& matches!(bytes[2], b'\\' | b'/');
|
||||
|
||||
starts_with_drive_root || path.starts_with(r"\\")
|
||||
}
|
||||
|
||||
impl CustomTheme {
|
||||
pub fn new(s: String, p: PathBuf) -> Self {
|
||||
CustomTheme { name: s, path: p }
|
||||
@@ -284,6 +454,7 @@ impl InMemoryThemeOptions {
|
||||
// Note that, as an invariant, in-memory themes come from local files.
|
||||
source: AssetSource::LocalFile {
|
||||
path: self.path().to_str().unwrap_or_default().to_owned(),
|
||||
content_version: None,
|
||||
},
|
||||
opacity: 30,
|
||||
}),
|
||||
@@ -571,5 +742,5 @@ pub fn render_preview(
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "theme_test.rs"]
|
||||
#[path = "theme_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
Reference in New Issue
Block a user