first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+199 -28
View File
@@ -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;