diff --git a/crates/benilla-ui/src/script/colorselect.rs b/crates/benilla-ui/src/script/colorselect.rs index e92e8b60..375da367 100644 --- a/crates/benilla-ui/src/script/colorselect.rs +++ b/crates/benilla-ui/src/script/colorselect.rs @@ -144,6 +144,14 @@ fn install_slot_texture( let mut model = lua.app_data_mut::().expect("model app_data"); let data = model.region_data.entry(rh).or_default(); match &a1 { + // `""` clears, as `SetTexture`'s own path arm does (`0x770200`). See the + // note on `SetStatusBarTexture`: stored, an empty path reaches the extractor + // as `path: Some("")` and the renderer logs a `texture miss: ''` for a file + // nobody named. + Value::String(s) if s.to_str()?.is_empty() => { + data.texture = None; + data.fill = None; + } Value::String(s) => { data.texture = Some(s.to_str()?.to_string()); data.fill = None; diff --git a/crates/benilla-ui/src/script/slider.rs b/crates/benilla-ui/src/script/slider.rs index ac2a4299..bb0dbede 100644 --- a/crates/benilla-ui/src/script/slider.rs +++ b/crates/benilla-ui/src/script/slider.rs @@ -169,6 +169,14 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> { let mut model = lua.app_data_mut::().expect("model app_data"); let data = model.region_data.entry(rh).or_default(); match &a1 { + // `""` clears, as `SetTexture`'s own path arm does (`0x770200`). See the + // note on `SetStatusBarTexture`: stored, an empty path reaches the extractor + // as `path: Some("")` and the renderer logs a `texture miss: ''` for a file + // nobody named. + Value::String(s) if s.to_str()?.is_empty() => { + data.texture = None; + data.fill = None; + } Value::String(s) => { data.texture = Some(s.to_str()?.to_string()); data.fill = None; diff --git a/crates/benilla-ui/src/script/statusbar.rs b/crates/benilla-ui/src/script/statusbar.rs index 3f18e329..8369576c 100644 --- a/crates/benilla-ui/src/script/statusbar.rs +++ b/crates/benilla-ui/src/script/statusbar.rs @@ -160,6 +160,17 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> { let mut model = lua.app_data_mut::().expect("model app_data"); let data = model.region_data.entry(rh).or_default(); match &a1 { + // `""` clears, as `SetTexture`'s path arm does (`0x770200`: "nil or `\"\"` + // clear it") and as the button slot setter does. Stored, it reached the + // extractor as `path: Some("")`, so the renderer asked the patch chain for a + // nameless file and logged `texture miss: ''`. Both the decode and the warning + // are cached per key, so the cost was one lock and two candidate reads, once — + // the damage was the warning itself, a real-looking signal with no defect + // under it. pfUI blanks textures this way in a dozen places. + Value::String(s) if s.to_str()?.is_empty() => { + data.texture = None; + data.fill = None; + } Value::String(s) => { data.texture = Some(s.to_str()?.to_string()); data.fill = None; diff --git a/crates/benilla-ui/src/script/tests/statusbar.rs b/crates/benilla-ui/src/script/tests/statusbar.rs index 62eb5925..dc0abf4d 100644 --- a/crates/benilla-ui/src/script/tests/statusbar.rs +++ b/crates/benilla-ui/src/script/tests/statusbar.rs @@ -195,3 +195,62 @@ fn statusbar_get_texture_returns_the_bar_region() { ) .unwrap(); } + +/// `""` clears, on every setter that takes a texture path — not just `SetTexture`. +/// +/// The reference's path arm treats nil and `""` alike (`0x770200`), and ours did for +/// `Texture:SetTexture` and for a button's slot textures while `SetStatusBarTexture`, +/// `SetThumbTexture` and the two `ColorSelect` setters stored the empty string. Stored, it reached +/// the extractor as `path: Some("")`, and the renderer then asked the patch chain for a nameless +/// file and logged `texture miss: ''`. The decode and the warning are both cached per key, so the +/// cost was one lock and two candidate reads once — what it really produced was a defect-shaped +/// warning with no defect under it, in a client where a real `texture miss` is worth reading. +/// pfUI blanks textures this way in a dozen places. +#[test] +fn an_empty_path_clears_every_texture_setter_not_only_set_texture() { + let mut s = script(); + s.set_screen_size(1024.0, 768.0); + s.run( + r#" + Blanked = {} + local b = CreateFrame("StatusBar", "SbBlank") + b:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, 0) + b:SetWidth(200); b:SetHeight(20) + b:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar") + b:SetMinMaxValues(0, 100) b:SetValue(100) + table.insert(Blanked, function() b:SetStatusBarTexture("") end) + + local sl = CreateFrame("Slider", "SlBlank") + sl:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, 40) + sl:SetWidth(100); sl:SetHeight(16) + sl:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Horizontal") + table.insert(Blanked, function() sl:SetThumbTexture("") end) + + local cs = CreateFrame("ColorSelect", "CsBlank") + cs:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 0, 80) + cs:SetWidth(128); cs:SetHeight(128) + cs:SetColorWheelTexture("Interface\\Buttons\\UI-ColorPicker-Buttons") + table.insert(Blanked, function() cs:SetColorWheelTexture("") end) + "#, + ) + .unwrap(); + s.resolve(); + let paths = |s: &UiScript| -> Vec { + s.extract() + .iter() + .filter_map(|q| match &q.content { + QuadContent::Texture { path: Some(p), .. } => Some(p.clone()), + _ => None, + }) + .collect() + }; + assert_eq!(paths(&s).len(), 3, "three textured regions to begin with"); + + s.run("for _, f in ipairs(Blanked) do f() end").unwrap(); + s.resolve(); + assert!( + paths(&s).is_empty(), + "an empty path must leave no textured quad at all, let alone a nameless one: {:?}", + paths(&s) + ); +}