mirror of
https://github.com/nekename/OpenDeck.git
synced 2026-10-28 09:44:01 +00:00
fix: prevent custom action state images and text from being overriden by plugin events
This commit is contained in:
1 parent
5de234fd2b
commit
772189ca74
9 files changed
+82
-41
No files matched your search
@@ -61,17 +61,17 @@ fn get_encoder_image(encoder: &mut Encoder, instance: &ActionInstance) -> Result
|
||||
|
||||
// Override the title if it's set in the state.
|
||||
let override_title = {
|
||||
let t = instance.states[state_idx].text.trim();
|
||||
(!t.is_empty()).then(|| t.to_string())
|
||||
let t = instance.states[state_idx]
|
||||
.text_override
|
||||
.clone()
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or_else(|| instance.states[state_idx].text.clone());
|
||||
(!t.is_empty()).then_some(t)
|
||||
};
|
||||
|
||||
// Similar to the title, except for the rendered icon (we check whether the state matches the default, and if it doesn't, we use it).
|
||||
// Similar to the title, except for the rendered icon.
|
||||
let state_image = &instance.states[state_idx].image.trim();
|
||||
|
||||
let override_icon = {
|
||||
let default_image = &instance.action.states[state_idx].image.trim();
|
||||
(!state_image.is_empty() && state_image != default_image).then(|| state_image.to_string())
|
||||
};
|
||||
let override_icon = instance.states[state_idx].image_override.clone();
|
||||
|
||||
// We need to do small item corrections here.
|
||||
let mut feedback = Map::new();
|
||||
|
||||
@@ -130,6 +130,13 @@ pub async fn move_instance(source: Context, destination: Context, retain: bool)
|
||||
if path.starts_with(&old_dir) {
|
||||
state.image = new_dir.join(path.strip_prefix(&old_dir).unwrap()).to_string_lossy().into_owned();
|
||||
}
|
||||
|
||||
if let Some(image_override) = state.image_override.as_mut() {
|
||||
let override_path = std::path::Path::new(image_override);
|
||||
if override_path.starts_with(&old_dir) {
|
||||
*image_override = new_dir.join(override_path.strip_prefix(&old_dir).unwrap()).to_string_lossy().into_owned();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dst = get_slot_mut(&destination, &mut locks).await?;
|
||||
|
||||
@@ -56,7 +56,7 @@ pub async fn title_parameters_did_change(instance: &ActionInstance, state: u16)
|
||||
column: instance.context.position % 3,
|
||||
},
|
||||
state: instance.current_state,
|
||||
title: state.text,
|
||||
title: state.text_override.filter(|s| !s.is_empty()).unwrap_or(state.text),
|
||||
titleParameters: TitleParameters {
|
||||
fontFamily: String::new(),
|
||||
fontSize: state.size.0,
|
||||
|
||||
@@ -128,6 +128,9 @@ pub struct ActionState {
|
||||
#[serde(alias = "Image")]
|
||||
pub image: String,
|
||||
// Note: this is not a real manifest property; it is only used internally.
|
||||
#[serde(alias = "ImageOverride")]
|
||||
pub image_override: Option<String>,
|
||||
// Note: this is not a real manifest property; it is only used internally.
|
||||
#[serde(alias = "ImageScale")]
|
||||
pub image_scale: u8,
|
||||
// Note: this is not a real manifest property; it is only used internally.
|
||||
@@ -137,6 +140,9 @@ pub struct ActionState {
|
||||
pub name: String,
|
||||
#[serde(alias = "Title")]
|
||||
pub text: String,
|
||||
// Note: this is not a real manifest property; it is only used internally.
|
||||
#[serde(alias = "TitleOverride")]
|
||||
pub text_override: Option<String>,
|
||||
#[serde(alias = "ShowTitle")]
|
||||
pub show: bool,
|
||||
#[serde(alias = "TitleColor")]
|
||||
@@ -163,10 +169,12 @@ impl Default for ActionState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
image: "actionDefaultImage".to_owned(),
|
||||
image_override: None,
|
||||
image_scale: 100,
|
||||
background_colour: "#000000".to_owned(),
|
||||
name: String::new(),
|
||||
text: String::new(),
|
||||
text_override: None,
|
||||
show: true,
|
||||
colour: "#FFFFFF".to_owned(),
|
||||
stroke_colour: "#000000".to_owned(),
|
||||
|
||||
@@ -87,13 +87,13 @@ impl From<ActionInstance> for DiskActionInstance {
|
||||
}
|
||||
};
|
||||
|
||||
for (index, state) in value.states.iter_mut().enumerate() {
|
||||
if state.image.trim() == "data:" {
|
||||
state.image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2OUAAAAABJRU5ErkJggg==".to_owned();
|
||||
let normalise_image = |index: usize, image: &mut String, suffix: &str| {
|
||||
if image.trim() == "data:" {
|
||||
*image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYGD4DwABBAEAwS2OUAAAAABJRU5ErkJggg==".to_owned();
|
||||
}
|
||||
|
||||
if state.image.starts_with("data:") && state.image.contains('/') && state.image.contains(',') {
|
||||
let mut extension = state.image.split_once('/').unwrap().1.split_once(',').unwrap().0;
|
||||
if image.starts_with("data:") && image.contains('/') && image.contains(',') {
|
||||
let mut extension = image.split_once('/').unwrap().1.split_once(',').unwrap().0;
|
||||
if extension.contains(';') {
|
||||
extension = extension.split_once(';').unwrap().0;
|
||||
}
|
||||
@@ -101,24 +101,35 @@ impl From<ActionInstance> for DiskActionInstance {
|
||||
extension = extension.split_once('+').unwrap().0;
|
||||
}
|
||||
|
||||
let data = if state.image.contains(";base64,") {
|
||||
let data = if image.contains(";base64,") {
|
||||
use base64::Engine;
|
||||
let Ok(data) = base64::engine::general_purpose::STANDARD.decode(state.image.split_once(";base64,").unwrap().1) else {
|
||||
continue;
|
||||
let Ok(data) = base64::engine::general_purpose::STANDARD.decode(image.split_once(";base64,").unwrap().1) else {
|
||||
return;
|
||||
};
|
||||
data
|
||||
} else {
|
||||
state.image.split_once(',').unwrap().1.as_bytes().to_vec()
|
||||
image.split_once(',').unwrap().1.as_bytes().to_vec()
|
||||
};
|
||||
|
||||
let filename = format!("{}.{}", index, extension);
|
||||
let filename = format!("{}{}.{}", index, suffix, extension);
|
||||
if fs::create_dir_all(&image_dir).is_err() || fs::write(image_dir.join(&filename), data).is_err() {
|
||||
continue;
|
||||
return;
|
||||
};
|
||||
state.image = filename;
|
||||
*image = filename;
|
||||
}
|
||||
|
||||
state.image = normalise_path(&state.image);
|
||||
*image = normalise_path(image);
|
||||
};
|
||||
|
||||
for (index, state) in value.states.iter_mut().enumerate() {
|
||||
if state.text_override.as_ref().map(|s| s.is_empty()).unwrap_or(false) {
|
||||
state.text_override = None;
|
||||
}
|
||||
|
||||
if let Some(ref mut image) = state.image_override {
|
||||
normalise_image(index, image, "-user");
|
||||
}
|
||||
normalise_image(index, &mut state.image, "");
|
||||
}
|
||||
for state in value.action.states.iter_mut() {
|
||||
state.image = normalise_path(&state.image);
|
||||
@@ -153,20 +164,27 @@ impl DiskActionInstance {
|
||||
}
|
||||
};
|
||||
|
||||
let mut states = self.states.clone();
|
||||
for state in states.iter_mut() {
|
||||
if let Some(true) = state.image.chars().next().map(|v| v.is_numeric()) {
|
||||
state.image = config_dir
|
||||
let reconstruct_image = |image: &mut String| {
|
||||
if let Some(true) = image.chars().next().map(|v| v.is_numeric()) {
|
||||
*image = config_dir
|
||||
.join("images")
|
||||
.join(&device)
|
||||
.join(&profile)
|
||||
.join(self.context.to_string())
|
||||
.join(&state.image)
|
||||
.join(&image)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
} else {
|
||||
state.image = reconstruct_path(&state.image);
|
||||
*image = reconstruct_path(image);
|
||||
}
|
||||
};
|
||||
|
||||
let mut states = self.states.clone();
|
||||
for state in states.iter_mut() {
|
||||
if let Some(ref mut image) = state.image_override {
|
||||
reconstruct_image(image);
|
||||
}
|
||||
reconstruct_image(&mut state.image);
|
||||
}
|
||||
let mut action = self.action.clone();
|
||||
for state in action.states.iter_mut() {
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
let result = reader.result?.toString();
|
||||
if (result) {
|
||||
let resized = await resizeImage(result);
|
||||
if (resized) instance.states[state].image = resized;
|
||||
else instance.states[state].image = result;
|
||||
if (resized) instance.states[state].image_override = resized;
|
||||
else instance.states[state].image_override = result;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -89,7 +89,10 @@
|
||||
on:drop={handleDrop}
|
||||
on:contextmenu={(event) => {
|
||||
event.preventDefault();
|
||||
instance.states[state].image = instance.action.states[state]?.image ?? instance.action.icon;
|
||||
if (instance.states[state].image_override) instance.states[state].image_override = null;
|
||||
// Support further resetting the base image to the manifest value as a migration path
|
||||
// for profiles where the base image and user override image were indistinguishable.
|
||||
else instance.states[state].image = instance.action.states[state]?.image ?? instance.action.icon;
|
||||
}}
|
||||
title={$t("instance_editor.image.hint")}
|
||||
aria-label={$t("instance_editor.image.hint")}
|
||||
@@ -173,7 +176,7 @@
|
||||
if (!context) return;
|
||||
context.fillStyle = solidColourInput.value;
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
instance.states[state].image = canvas.toDataURL("image/png");
|
||||
instance.states[state].image_override = canvas.toDataURL("image/png");
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
@@ -191,8 +194,8 @@
|
||||
let result = reader.result?.toString();
|
||||
if (result) {
|
||||
let resized = await resizeImage(result);
|
||||
if (resized) instance.states[state].image = resized;
|
||||
else instance.states[state].image = result;
|
||||
if (resized) instance.states[state].image_override = resized;
|
||||
else instance.states[state].image_override = result;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -204,8 +207,8 @@
|
||||
<div class="flex flex-row items-center space-x-2">
|
||||
<label for="editor-text">{$t("instance_editor.text")}</label>
|
||||
<textarea
|
||||
bind:value={instance.states[state].text}
|
||||
placeholder={instance.action.states[state]?.text || instance.action.name}
|
||||
bind:value={instance.states[state].text_override}
|
||||
placeholder={instance.states[state].text}
|
||||
rows="1"
|
||||
class="w-full px-1 text-neutral-300 bg-neutral-600 border border-neutral-500 rounded-lg resize-none"
|
||||
id="editor-text"
|
||||
|
||||
@@ -188,7 +188,8 @@
|
||||
await invoke("trigger_virtual_press", { context });
|
||||
}
|
||||
|
||||
$: accessibleLabel = label + (slot ? ": " + slot.action.name + (state?.show && state?.text ? " - " + state.text : "") : "");
|
||||
$: accessibleLabel =
|
||||
label + (slot ? ": " + slot.action.name + (state?.show && (state?.text_override || state?.text) ? " - " + (state.text_override || state.text) : "") : "");
|
||||
</script>
|
||||
|
||||
<div class="relative" style={`transform: scale(${(112 /* desired inner size */ / size) * scale});`}>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
export type ActionState = {
|
||||
image: string;
|
||||
image_override: string | null;
|
||||
image_scale: number;
|
||||
background_colour: string;
|
||||
name: string;
|
||||
text: string;
|
||||
text_override: string | null;
|
||||
show: boolean;
|
||||
colour: string;
|
||||
stroke_colour: string;
|
||||
|
||||
@@ -76,7 +76,8 @@ export async function renderImage(
|
||||
// Load image
|
||||
const image = document.createElement("img");
|
||||
image.crossOrigin = "anonymous";
|
||||
image.src = processImage ? getImage(state.image, fallback) : state.image;
|
||||
const imageSource = state.image_override ?? state.image;
|
||||
image.src = processImage ? getImage(imageSource, fallback) : imageSource;
|
||||
if (image.src == undefined) return;
|
||||
await new Promise((resolve, reject) => {
|
||||
image.onload = resolve;
|
||||
@@ -107,6 +108,7 @@ export async function renderImage(
|
||||
|
||||
// Draw text
|
||||
if (state.show) {
|
||||
const textSource = state.text_override || state.text;
|
||||
const size = state.size * 2 * scale;
|
||||
context.textAlign = "center";
|
||||
context.font =
|
||||
@@ -116,16 +118,16 @@ export async function renderImage(
|
||||
context.lineWidth = state.stroke_size * scale;
|
||||
context.textBaseline = "top";
|
||||
const x = canvas.width / 2;
|
||||
let y = canvas.height / 2 - size * state.text.split("\n").length * 0.5;
|
||||
let y = canvas.height / 2 - size * textSource.split("\n").length * 0.5;
|
||||
switch (state.alignment) {
|
||||
case "top":
|
||||
y = context.lineWidth;
|
||||
break;
|
||||
case "bottom":
|
||||
y = canvas.height - size * state.text.split("\n").length - context.lineWidth;
|
||||
y = canvas.height - size * textSource.split("\n").length - context.lineWidth;
|
||||
break;
|
||||
}
|
||||
for (const [index, line] of Object.entries(state.text.split("\n"))) {
|
||||
for (const [index, line] of Object.entries(textSource.split("\n"))) {
|
||||
context.strokeText(line, x, y + size * parseInt(index));
|
||||
context.fillText(line, x, y + size * parseInt(index));
|
||||
if (state.underline) {
|
||||
|
||||
Reference in new issue
Block a user