Synchronize shell material presentation with surface commits
Queue popup, modal and notification material geometry and opacity until the matching content buffer commits. Remove event-handler opacity writes that could present material state ahead of content, while preserving compositor dismissal ownership. Add pending-presentation regression coverage and update the nested shell lifecycle check.
This commit is contained in:
1 parent
e6e1a2a8cf
commit
bffd538dd4
9 files changed
+159
-50
No files matched your search
+104
-10
@@ -51,6 +51,14 @@ pub(crate) fn resolve_material(role: SemanticRole, style: MaterialStyle, opacity
|
|||||||
ResolvedMaterial { style, opacity, shadow }
|
ResolvedMaterial { style, opacity, shadow }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PendingPresentation {
|
||||||
|
role: SemanticRole,
|
||||||
|
regions: Vec<[f64; 5]>,
|
||||||
|
opacity: u32,
|
||||||
|
region_opacities: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SurfaceEffectsState {
|
struct SurfaceEffectsState {
|
||||||
attached: AtomicBool,
|
attached: AtomicBool,
|
||||||
@@ -58,6 +66,7 @@ struct SurfaceEffectsState {
|
|||||||
role: Mutex<Option<SemanticRole>>,
|
role: Mutex<Option<SemanticRole>>,
|
||||||
regions: Mutex<Option<Vec<[f64; 5]>>>,
|
regions: Mutex<Option<Vec<[f64; 5]>>>,
|
||||||
opacity: Mutex<u32>,
|
opacity: Mutex<u32>,
|
||||||
|
pending: Mutex<Option<PendingPresentation>>,
|
||||||
region_opacities: Mutex<Vec<u32>>,
|
region_opacities: Mutex<Vec<u32>>,
|
||||||
presentation_supported: AtomicBool,
|
presentation_supported: AtomicBool,
|
||||||
dismissing: AtomicBool,
|
dismissing: AtomicBool,
|
||||||
@@ -71,12 +80,26 @@ impl SurfaceEffectsState {
|
|||||||
role: Mutex::new(None),
|
role: Mutex::new(None),
|
||||||
regions: Mutex::new(None),
|
regions: Mutex::new(None),
|
||||||
opacity: Mutex::new(1000),
|
opacity: Mutex::new(1000),
|
||||||
|
pending: Mutex::new(None),
|
||||||
region_opacities: Mutex::new(Vec::new()),
|
region_opacities: Mutex::new(Vec::new()),
|
||||||
presentation_supported: AtomicBool::new(false),
|
presentation_supported: AtomicBool::new(false),
|
||||||
dismissing: AtomicBool::new(false),
|
dismissing: AtomicBool::new(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn commit_presentation(&self) {
|
||||||
|
let Some(pending) = self.pending.lock().unwrap().take() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*self.role.lock().unwrap() = Some(pending.role);
|
||||||
|
*self.regions.lock().unwrap() = Some(pending.regions);
|
||||||
|
*self.region_opacities.lock().unwrap() = pending.region_opacities;
|
||||||
|
if !self.dismissing.load(Ordering::Acquire) {
|
||||||
|
*self.opacity.lock().unwrap() = pending.opacity;
|
||||||
|
}
|
||||||
|
self.generation.fetch_add(1, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
fn set_role(&self, role: Option<SemanticRole>) -> bool {
|
fn set_role(&self, role: Option<SemanticRole>) -> bool {
|
||||||
let mut current = self.role.lock().unwrap();
|
let mut current = self.role.lock().unwrap();
|
||||||
if *current != role {
|
if *current != role {
|
||||||
@@ -103,7 +126,7 @@ impl SurfaceEffectsUserData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn init_global(display: &DisplayHandle) {
|
pub(crate) fn init_global(display: &DisplayHandle) {
|
||||||
display.create_global::<Ferese, FereseEffectsManagerV1, _>(4, ());
|
display.create_global::<Ferese, FereseEffectsManagerV1, _>(5, ());
|
||||||
display.create_global::<Ferese, FereseMaterialManagerV1, _>(1, ());
|
display.create_global::<Ferese, FereseMaterialManagerV1, _>(1, ());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +208,14 @@ impl Dispatch<FereseSurfaceMaterialV1, SurfaceEffectsUserData> for Ferese {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn commit_presentation(surface: &WlSurface) {
|
||||||
|
with_states(surface, |states| {
|
||||||
|
if let Some(effects) = states.data_map.get::<SurfaceEffectsState>() {
|
||||||
|
effects.commit_presentation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn surface_regions(surface: &WlSurface) -> Option<Vec<[f64; 5]>> {
|
pub(crate) fn surface_regions(surface: &WlSurface) -> Option<Vec<[f64; 5]>> {
|
||||||
with_states(surface, |states| {
|
with_states(surface, |states| {
|
||||||
states
|
states
|
||||||
@@ -330,6 +361,30 @@ impl Dispatch<FereseSurfaceEffectsV1, SurfaceEffectsUserData> for Ferese {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match request {
|
match request {
|
||||||
|
ferese_surface_effects_v1::Request::SetPresentation {
|
||||||
|
role,
|
||||||
|
regions,
|
||||||
|
opacity,
|
||||||
|
region_opacities,
|
||||||
|
} => {
|
||||||
|
let (Some(role), Some(regions), Some(region_opacities)) = (
|
||||||
|
decode_role(role),
|
||||||
|
decode_regions(®ions),
|
||||||
|
decode_region_opacities(®ion_opacities),
|
||||||
|
) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
with_states(&surface, |states| {
|
||||||
|
if let Some(effects) = states.data_map.get::<SurfaceEffectsState>() {
|
||||||
|
*effects.pending.lock().unwrap() = Some(PendingPresentation {
|
||||||
|
role,
|
||||||
|
regions,
|
||||||
|
opacity: opacity.min(1000),
|
||||||
|
region_opacities,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
ferese_surface_effects_v1::Request::SetOpacity { opacity } => {
|
ferese_surface_effects_v1::Request::SetOpacity { opacity } => {
|
||||||
let changed = with_states(&surface, |states| {
|
let changed = with_states(&surface, |states| {
|
||||||
let Some(effects) = states.data_map.get::<SurfaceEffectsState>() else {
|
let Some(effects) = states.data_map.get::<SurfaceEffectsState>() else {
|
||||||
@@ -352,15 +407,8 @@ impl Dispatch<FereseSurfaceEffectsV1, SurfaceEffectsUserData> for Ferese {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ferese_surface_effects_v1::Request::SetRole { role } => {
|
ferese_surface_effects_v1::Request::SetRole { role } => {
|
||||||
let role = match role {
|
let Some(role) = decode_role(role) else {
|
||||||
WEnum::Value(ferese_surface_effects_v1::Role::Panel) => SemanticRole::Panel,
|
return;
|
||||||
WEnum::Value(ferese_surface_effects_v1::Role::PanelElevated) => SemanticRole::PanelElevated,
|
|
||||||
WEnum::Value(ferese_surface_effects_v1::Role::Popover) => SemanticRole::Popover,
|
|
||||||
WEnum::Value(ferese_surface_effects_v1::Role::Menu) => SemanticRole::Menu,
|
|
||||||
WEnum::Value(ferese_surface_effects_v1::Role::Notification) => SemanticRole::Notification,
|
|
||||||
WEnum::Value(ferese_surface_effects_v1::Role::Hud) => SemanticRole::Hud,
|
|
||||||
WEnum::Value(ferese_surface_effects_v1::Role::Modal) => SemanticRole::Modal,
|
|
||||||
WEnum::Unknown(_) | WEnum::Value(_) => return,
|
|
||||||
};
|
};
|
||||||
if set_surface_role(&surface, Some(role)) {
|
if set_surface_role(&surface, Some(role)) {
|
||||||
crate::backends::direct::render_surface(state, &surface);
|
crate::backends::direct::render_surface(state, &surface);
|
||||||
@@ -432,6 +480,19 @@ impl Dispatch<FereseSurfaceEffectsV1, SurfaceEffectsUserData> for Ferese {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_role(role: WEnum<ferese_surface_effects_v1::Role>) -> Option<SemanticRole> {
|
||||||
|
Some(match role {
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Panel) => SemanticRole::Panel,
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::PanelElevated) => SemanticRole::PanelElevated,
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Popover) => SemanticRole::Popover,
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Menu) => SemanticRole::Menu,
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Notification) => SemanticRole::Notification,
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Hud) => SemanticRole::Hud,
|
||||||
|
WEnum::Value(ferese_surface_effects_v1::Role::Modal) => SemanticRole::Modal,
|
||||||
|
WEnum::Unknown(_) | WEnum::Value(_) => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn set_surface_role(surface: &WlSurface, role: Option<SemanticRole>) -> bool {
|
fn set_surface_role(surface: &WlSurface, role: Option<SemanticRole>) -> bool {
|
||||||
with_states(surface, |states| {
|
with_states(surface, |states| {
|
||||||
states
|
states
|
||||||
@@ -448,6 +509,7 @@ fn detach(surface: &WlSurface) -> bool {
|
|||||||
};
|
};
|
||||||
let role_changed = effects.set_role(None);
|
let role_changed = effects.set_role(None);
|
||||||
effects.region_opacities.lock().unwrap().clear();
|
effects.region_opacities.lock().unwrap().clear();
|
||||||
|
effects.pending.lock().unwrap().take();
|
||||||
effects.dismissing.store(false, Ordering::Release);
|
effects.dismissing.store(false, Ordering::Release);
|
||||||
let mut opacity = effects.opacity.lock().unwrap();
|
let mut opacity = effects.opacity.lock().unwrap();
|
||||||
let opacity_changed = *opacity != 1000;
|
let opacity_changed = *opacity != 1000;
|
||||||
@@ -489,6 +551,38 @@ pub(crate) fn fade_dismissed_surface(surface: &WlSurface, opacity: f64) {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn presentation_waits_for_buffer_commit_and_keeps_dismissal_ownership() {
|
||||||
|
let state = SurfaceEffectsState::new();
|
||||||
|
let queue = |opacity| PendingPresentation {
|
||||||
|
role: SemanticRole::Popover,
|
||||||
|
regions: vec![[1.25, 2.5, 100.0, 80.0, 12.0]],
|
||||||
|
opacity,
|
||||||
|
region_opacities: vec![500],
|
||||||
|
};
|
||||||
|
*state.pending.lock().unwrap() = Some(queue(900));
|
||||||
|
assert_eq!(*state.opacity.lock().unwrap(), 1000);
|
||||||
|
assert!(state.regions.lock().unwrap().is_none());
|
||||||
|
assert_eq!(state.generation.load(Ordering::Acquire), 0);
|
||||||
|
// Several requests before one buffer commit present only the last.
|
||||||
|
*state.pending.lock().unwrap() = Some(queue(800));
|
||||||
|
state.commit_presentation();
|
||||||
|
assert_eq!(*state.opacity.lock().unwrap(), 800);
|
||||||
|
assert_eq!(state.regions.lock().unwrap().as_ref().unwrap()[0][0], 1.25);
|
||||||
|
assert_eq!(*state.region_opacities.lock().unwrap(), [500]);
|
||||||
|
assert_eq!(state.generation.load(Ordering::Acquire), 1);
|
||||||
|
state.commit_presentation();
|
||||||
|
assert_eq!(state.generation.load(Ordering::Acquire), 1);
|
||||||
|
state.dismissing.store(true, Ordering::Release);
|
||||||
|
*state.pending.lock().unwrap() = Some(queue(1000));
|
||||||
|
state.commit_presentation();
|
||||||
|
assert_eq!(
|
||||||
|
*state.opacity.lock().unwrap(),
|
||||||
|
800,
|
||||||
|
"client cannot restart compositor fade"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn region_opacities_validate_independent_fades() {
|
fn region_opacities_validate_independent_fades() {
|
||||||
let bytes: Vec<_> = [0u32, 500, 1000].into_iter().flat_map(u32::to_ne_bytes).collect();
|
let bytes: Vec<_> = [0u32, 500, 1000].into_iter().flat_map(u32::to_ne_bytes).collect();
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ impl CompositorHandler for Ferese {
|
|||||||
self.capture_resize_before_commit(surface);
|
self.capture_resize_before_commit(surface);
|
||||||
self.capture_close_before_commit(surface);
|
self.capture_close_before_commit(surface);
|
||||||
on_commit_buffer_handler::<Self>(surface);
|
on_commit_buffer_handler::<Self>(surface);
|
||||||
|
crate::effects::commit_presentation(surface);
|
||||||
if !is_sync_subsurface(surface) {
|
if !is_sync_subsurface(surface) {
|
||||||
let mut root = surface.clone();
|
let mut root = surface.clone();
|
||||||
while let Some(parent) = get_parent(&root) {
|
while let Some(parent) = get_parent(&root) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
</copyright>
|
</copyright>
|
||||||
|
|
||||||
<interface name="ferese_effects_manager_v1" version="4">
|
<interface name="ferese_effects_manager_v1" version="5">
|
||||||
<description summary="assign semantic material roles to surfaces">
|
<description summary="assign semantic material roles to surfaces">
|
||||||
This private global is visible only to clients granted the Ferese effects
|
This private global is visible only to clients granted the Ferese effects
|
||||||
capability. Roles describe compositor-owned appearance policy and do not
|
capability. Roles describe compositor-owned appearance policy and do not
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
</enum>
|
</enum>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
||||||
<interface name="ferese_surface_effects_v1" version="4">
|
<interface name="ferese_surface_effects_v1" version="5">
|
||||||
<description summary="semantic material role for one surface">
|
<description summary="semantic material role for one surface">
|
||||||
The role affects only compositor rendering. It does not change surface
|
The role affects only compositor rendering. It does not change surface
|
||||||
geometry, input, focus, stacking, or the surface's Wayland role.
|
geometry, input, focus, stacking, or the surface's Wayland role.
|
||||||
@@ -107,5 +107,19 @@
|
|||||||
</description>
|
</description>
|
||||||
<arg name="opacities" type="array"/>
|
<arg name="opacities" type="array"/>
|
||||||
</request>
|
</request>
|
||||||
|
<request name="set_presentation" since="5">
|
||||||
|
<description summary="apply material presentation with the next surface commit">
|
||||||
|
Queue the role, regions, surface opacity and region opacities together.
|
||||||
|
They take effect on the next wl_surface.commit, with its content buffer.
|
||||||
|
Array formats and limits match set_regions and set_region_opacities.
|
||||||
|
Invalid arrays or roles leave the pending state unchanged. Surface opacity
|
||||||
|
is clamped to 1000. The last valid request before a commit wins.
|
||||||
|
Compositor-owned dismissal retains control of surface opacity.
|
||||||
|
</description>
|
||||||
|
<arg name="role" type="uint" enum="role"/>
|
||||||
|
<arg name="regions" type="array"/>
|
||||||
|
<arg name="opacity" type="uint"/>
|
||||||
|
<arg name="region_opacities" type="array"/>
|
||||||
|
</request>
|
||||||
</interface>
|
</interface>
|
||||||
</protocol>
|
</protocol>
|
||||||
@@ -119,10 +119,10 @@ class ShellMotionTest(unittest.TestCase):
|
|||||||
# Clock/status updates still redraw settled surfaces. Count
|
# Clock/status updates still redraw settled surfaces. Count
|
||||||
# commits: Iced can request two callbacks for one redraw.
|
# commits: Iced can request two callbacks for one redraw.
|
||||||
self.assertLessEqual(idle_frames, 12, "settled toast kept requesting animation frames")
|
self.assertLessEqual(idle_frames, 12, "settled toast kept requesting animation frames")
|
||||||
material_updates = log_path.read_text().count('.set_region_opacities(')
|
material_updates = log_path.read_text().count('.set_presentation(')
|
||||||
dbus("CloseNotification", notice)
|
dbus("CloseNotification", notice)
|
||||||
wait_for(lambda: destroyed(surface), timeout=5)
|
wait_for(lambda: destroyed(surface), timeout=5)
|
||||||
self.assertGreater(log_path.read_text().count('.set_region_opacities(') - material_updates, 5,
|
self.assertGreater(log_path.read_text().count('.set_presentation(') - material_updates, 5,
|
||||||
"notification materials did not follow their fade")
|
"notification materials did not follow their fade")
|
||||||
print(f"Toast: {opening_frames} opening callback requests, {idle_frames / 2:g} settled commits/s; destroyed", flush=True)
|
print(f"Toast: {opening_frames} opening callback requests, {idle_frames / 2:g} settled commits/s; destroyed", flush=True)
|
||||||
|
|
||||||
|
|||||||
@@ -524,12 +524,14 @@ impl FereseShell {
|
|||||||
if let Some(surface) = &self.notification_surface
|
if let Some(surface) = &self.notification_surface
|
||||||
&& let Some(effects) = &surface.effects
|
&& let Some(effects) = &surface.effects
|
||||||
{
|
{
|
||||||
let _ = effects.set_regions(&surface.regions.lock().unwrap());
|
let _ = effects.set_presentation(
|
||||||
let _ = effects.set_region_opacities(
|
&surface.regions.lock().unwrap(),
|
||||||
|
1.0,
|
||||||
self.notifications
|
self.notifications
|
||||||
.popup_groups_at(now)
|
.popup_groups_at(now)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, opacity, _)| *opacity),
|
.map(|(_, opacity, _)| *opacity),
|
||||||
|
ferese_surface_effects_v1::Role::Popover,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -145,9 +145,6 @@ impl FereseShell {
|
|||||||
|
|
||||||
pub fn animate_menu(&mut self) -> Task<Message> {
|
pub fn animate_menu(&mut self) -> Task<Message> {
|
||||||
if let Some(menu) = &self.menu {
|
if let Some(menu) = &self.menu {
|
||||||
if let Some(effects) = &menu.effects {
|
|
||||||
let _ = effects.set_opacity(menu.progress().clamp(0.0, 1.0));
|
|
||||||
}
|
|
||||||
if menu.motion.closing() && !menu.animating() {
|
if menu.motion.closing() && !menu.animating() {
|
||||||
return self.destroy_menu();
|
return self.destroy_menu();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ impl OpenMenu {
|
|||||||
super::EFFECT_FRAME_PENDING.store(false, Ordering::Relaxed);
|
super::EFFECT_FRAME_PENDING.store(false, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
pub fn progress(&self) -> f32 {
|
pub fn progress(&self) -> f32 {
|
||||||
self.motion.progress()
|
self.motion.progress()
|
||||||
}
|
}
|
||||||
@@ -138,8 +139,12 @@ impl FereseShell {
|
|||||||
|now| menu.motion.frame_active(now),
|
|now| menu.motion.frame_active(now),
|
||||||
|now| {
|
|now| {
|
||||||
if let Some(effects) = &menu.effects {
|
if let Some(effects) = &menu.effects {
|
||||||
let _ = effects.set_regions(&menu.regions.lock().unwrap());
|
let _ = effects.set_presentation(
|
||||||
let _ = effects.set_opacity(menu.motion.progress_at(now).clamp(0.0, 1.0));
|
&menu.regions.lock().unwrap(),
|
||||||
|
menu.motion.progress_at(now),
|
||||||
|
[],
|
||||||
|
super::ferese_surface_effects_v1::Role::Popover,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|now| {
|
|now| {
|
||||||
|
|||||||
@@ -333,9 +333,6 @@ impl FereseShell {
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
if let Some(effects) = &menu.effects {
|
|
||||||
let _ = effects.set_opacity(menu.progress());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
@@ -488,7 +485,7 @@ pub(super) struct EffectsBinding {
|
|||||||
_queue: EventQueue<EffectsState>,
|
_queue: EventQueue<EffectsState>,
|
||||||
regions: std::cell::RefCell<Option<Vec<[f32; 5]>>>,
|
regions: std::cell::RefCell<Option<Vec<[f32; 5]>>>,
|
||||||
opacity: std::cell::Cell<Option<u32>>,
|
opacity: std::cell::Cell<Option<u32>>,
|
||||||
region_opacities: std::cell::RefCell<Vec<u32>>,
|
presentation: std::cell::RefCell<Option<(Vec<[f32; 5]>, u32, Vec<u32>, ferese_surface_effects_v1::Role)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EffectsBinding {
|
impl EffectsBinding {
|
||||||
@@ -512,7 +509,7 @@ impl EffectsBinding {
|
|||||||
let connection = Connection::from_backend(backend);
|
let connection = Connection::from_backend(backend);
|
||||||
let (globals, queue) = registry_queue_init::<EffectsState>(&connection)?;
|
let (globals, queue) = registry_queue_init::<EffectsState>(&connection)?;
|
||||||
let qh = queue.handle();
|
let qh = queue.handle();
|
||||||
let manager = globals.bind::<FereseEffectsManagerV1, _, _>(&qh, 4..=4, ())?;
|
let manager = globals.bind::<FereseEffectsManagerV1, _, _>(&qh, 5..=5, ())?;
|
||||||
let effects = manager.get_surface_effects(surface, &qh, ());
|
let effects = manager.get_surface_effects(surface, &qh, ());
|
||||||
|
|
||||||
let opacity = (opacity.clamp(0.0, 1.0) * 1000.0).round() as u32;
|
let opacity = (opacity.clamp(0.0, 1.0) * 1000.0).round() as u32;
|
||||||
@@ -531,24 +528,33 @@ impl EffectsBinding {
|
|||||||
_queue: queue,
|
_queue: queue,
|
||||||
regions: Default::default(),
|
regions: Default::default(),
|
||||||
opacity: std::cell::Cell::new(Some(opacity)),
|
opacity: std::cell::Cell::new(Some(opacity)),
|
||||||
region_opacities: Default::default(),
|
presentation: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn set_region_opacities(
|
pub(super) fn set_presentation(
|
||||||
&self,
|
&self,
|
||||||
values: impl IntoIterator<Item = f32>,
|
regions: &[[f32; 5]],
|
||||||
|
opacity: f32,
|
||||||
|
region_opacities: impl IntoIterator<Item = f32>,
|
||||||
|
role: ferese_surface_effects_v1::Role,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let values: Vec<u32> = values
|
let opacity = (opacity.clamp(0.0, 1.0) * 1000.0).round() as u32;
|
||||||
|
let values: Vec<u32> = region_opacities
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.take(32)
|
.take(32)
|
||||||
.map(|value| (value.clamp(0.0, 1.0) * 1000.0).round() as u32)
|
.map(|value| (value.clamp(0.0, 1.0) * 1000.0).round() as u32)
|
||||||
.collect();
|
.collect();
|
||||||
if *self.region_opacities.borrow() != values {
|
let next = (regions.to_vec(), opacity, values, role);
|
||||||
self.surface
|
if self.presentation.borrow().as_ref() != Some(&next) {
|
||||||
.set_region_opacities(values.iter().flat_map(|value| value.to_ne_bytes()).collect());
|
self.surface.set_presentation(
|
||||||
|
role,
|
||||||
|
encode_regions(regions),
|
||||||
|
opacity,
|
||||||
|
next.2.iter().flat_map(|value| value.to_ne_bytes()).collect(),
|
||||||
|
);
|
||||||
self.connection.flush()?;
|
self.connection.flush()?;
|
||||||
*self.region_opacities.borrow_mut() = values;
|
*self.presentation.borrow_mut() = Some(next);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,18 +291,6 @@ impl FereseShell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn update_power_materials(&self) {
|
|
||||||
if let Some(modal) = &self.system_modal {
|
|
||||||
for entry in &modal.surfaces {
|
|
||||||
if let Some(effects) = &entry.effects {
|
|
||||||
let regions = entry.regions.lock().unwrap().clone();
|
|
||||||
let _ = effects.set_material_regions(®ions, ferese_surface_effects_v1::Role::Modal);
|
|
||||||
let _ = effects.set_opacity(modal.motion.progress().clamp(0.0, 1.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn close_system_modal(&mut self) -> Task<Message> {
|
pub(super) fn close_system_modal(&mut self) -> Task<Message> {
|
||||||
if self.system_modal.as_ref().is_some_and(SystemModal::is_display_mode) {
|
if self.system_modal.as_ref().is_some_and(SystemModal::is_display_mode) {
|
||||||
if self.display_mode.busy {
|
if self.display_mode.busy {
|
||||||
@@ -339,7 +327,6 @@ impl FereseShell {
|
|||||||
{
|
{
|
||||||
return self.destroy_system_modal(false);
|
return self.destroy_system_modal(false);
|
||||||
}
|
}
|
||||||
self.update_power_materials();
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,9 +470,12 @@ impl FereseShell {
|
|||||||
|now| modal.motion.frame_active(now),
|
|now| modal.motion.frame_active(now),
|
||||||
|now| {
|
|now| {
|
||||||
if let Some(effects) = &surface.effects {
|
if let Some(effects) = &surface.effects {
|
||||||
let _ = effects
|
let _ = effects.set_presentation(
|
||||||
.set_material_regions(&surface.regions.lock().unwrap(), ferese_surface_effects_v1::Role::Modal);
|
&surface.regions.lock().unwrap(),
|
||||||
let _ = effects.set_opacity(modal.motion.progress_at(now).clamp(0.0, 1.0));
|
modal.motion.progress_at(now),
|
||||||
|
[],
|
||||||
|
ferese_surface_effects_v1::Role::Modal,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|now| {
|
|now| {
|
||||||
|
|||||||
Reference in new issue
Block a user