Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 30 additions & 8 deletions desktop/src/band_presenter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{collections::HashMap, time::Duration};
use anyhow::{Result, bail};

use massive_applications::{InstanceId, ViewCreationInfo, ViewEvent, ViewId, ViewRole};
use massive_geometry::RectPx;
use massive_geometry::{RectPx, SizePx};
use massive_layout::{self as layout, LayoutAxis};
use massive_scene::Transform;
use massive_shell::Scene;
Expand All @@ -17,6 +17,7 @@ use crate::{
#[derive(Debug, Default)]
/// Manages the presentation of a horizontal band of instances.
pub struct BandPresenter {
// Robustness: don't make these pub.
pub instances: HashMap<InstanceId, InstancePresenter>,
/// The Instances in order as they take up space in a final configuration. Exiting
/// instances are not anymore in this list.
Expand All @@ -31,6 +32,15 @@ pub enum BandTarget {

impl BandPresenter {
pub const STRUCTURAL_ANIMATION_DURATION: Duration = Duration::from_millis(500);

pub fn presents_instance(&self, id: InstanceId) -> bool {
self.instances.contains_key(&id)
}

pub fn is_empty(&self) -> bool {
self.ordered.is_empty()
}

/// Present the primary instance and its primary role view.
///
/// For now this can not be done by separately presenting an instance and a view because we
Expand Down Expand Up @@ -67,21 +77,33 @@ impl BandPresenter {
}

/// Present an instance originating from another.
///
/// The originating is used for two purposes.
/// - For determining the panel size.
/// - For determining where to insert the new instance in the band (default is right next to
/// originating).
pub fn present_instance(
&mut self,
instance: InstanceId,
originating_from: InstanceId,
originating_from: Option<InstanceId>,
default_panel_size: SizePx,
scene: &Scene,
) -> Result<()> {
let Some(originating_presenter) = self.instances.get(&originating_from) else {
bail!("Originating presenter does not exist");
};
let originating_presenter =
originating_from.and_then(|originating_from| self.instances.get(&originating_from));

let presenter = InstancePresenter {
state: InstancePresenterState::Appearing,
panel_size: originating_presenter.panel_size,
panel_size: originating_presenter
.map(|p| p.panel_size)
.unwrap_or(default_panel_size),
rect: RectPx::zero(),
center_animation: scene.animated(originating_presenter.center_animation.value()),
// Correctness: We animate from 0,0 if no originating exist. Need a position here.
center_animation: scene.animated(
originating_presenter
.map(|op| op.center_animation.value())
.unwrap_or_default(),
),
};

if self.instances.insert(instance, presenter).is_some() {
Expand All @@ -91,7 +113,7 @@ impl BandPresenter {
let pos = self
.ordered
.iter()
.position(|i| *i == originating_from)
.position(|i| Some(*i) == originating_from)
.map(|i| i + 1)
.unwrap_or(self.ordered.len());

Expand Down
70 changes: 51 additions & 19 deletions desktop/src/desktop.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::time::Instant;

use anyhow::{Result, anyhow, bail};
use massive_geometry::SizePx;
use tokio::sync::mpsc::{UnboundedReceiver, unbounded_channel};
use uuid::Uuid;

Expand All @@ -13,7 +14,7 @@ use massive_renderer::RenderPacing;
use massive_shell::{ApplicationContext, FontManager, Scene, ShellEvent};
use massive_shell::{AsyncWindowRenderer, ShellWindow};

use crate::desktop_presenter::DesktopFocusPath;
use crate::desktop_presenter::{BandLocation, DesktopFocusPath};
use crate::projects::Project;
use crate::{
DesktopEnvironment, DesktopInteraction, DesktopPresenter, UserIntent,
Expand All @@ -27,6 +28,7 @@ pub struct Desktop {
scene: Scene,
renderer: AsyncWindowRenderer,
window: ShellWindow,
primary_instance_panel_size: SizePx,
presenter: DesktopPresenter,

event_manager: EventManager<ViewEvent>,
Expand Down Expand Up @@ -83,6 +85,7 @@ impl Desktop {
// Currently we can't target views directly, the focus system is targeting only instances
// and their primary view.
let primary_view = creation_info.id;
let default_size = creation_info.size();

let window = context.new_window(creation_info.size()).await?;
let renderer = window
Expand All @@ -100,7 +103,11 @@ impl Desktop {
instance_manager.add_view(primary_instance, &creation_info);

let ui = DesktopInteraction::new(
DesktopFocusPath::from_instance_and_view(primary_instance, primary_view),
DesktopFocusPath::from_instance_and_view(
BandLocation::TopBand,
primary_instance,
primary_view,
),
&instance_manager,
&mut presenter,
&scene,
Expand All @@ -111,9 +118,10 @@ impl Desktop {
scene,
renderer,
window,
primary_instance_panel_size: default_size,
presenter,
event_manager,
instance_manager,
presenter,
instance_commands: requests_rx,
context,
env,
Expand Down Expand Up @@ -185,38 +193,56 @@ impl Desktop {
match cmd {
UserIntent::None => {}
UserIntent::Focus(path) => {
self.interaction
.focus(path, &self.instance_manager, &mut self.presenter)?;
assert_eq!(
self.interaction
.focus(path, &self.instance_manager, &mut self.presenter)?,
UserIntent::None
);
}
UserIntent::StartInstance {
application,
originating_instance,
} => {
// Feature: Support starting non-primary applications.
let application = self
.env
.applications
.get_named(&application)
.get_named(&self.env.primary_application)
.ok_or(anyhow!("Internal error, application not registered"))?;

let instance = self
.instance_manager
.spawn(application, CreationMode::New)?;
self.presenter
.present_instance(instance, originating_instance, &self.scene)?;

// Simplify: Use the currently focused instance for determining the originating one.
let band_location = self
.interaction
.focused()
.band_location()
.expect("Failed to start an instance without a focused instance target");

self.presenter.present_instance(
band_location,
instance,
originating_instance,
self.primary_instance_panel_size,
&self.scene,
)?;

self.interaction.make_foreground(
band_location,
instance,
&self.instance_manager,
&mut self.presenter,
)?;
// Get default size from the first view or use a default
let default_size = self
.instance_manager
.views()
.next()
.map(|(_, info)| info.extents.size().cast())
.unwrap_or((800u32, 600u32).into());
self.presenter
.layout(default_size, true, &self.scene, &mut self.fonts.lock());

// Performance: We might not need a global re-layout, if we present an instance
// to the project's band (This has to work incremental some day).
self.presenter.layout(
self.primary_instance_panel_size,
true,
&self.scene,
&mut self.fonts.lock(),
);
}
UserIntent::StopInstance { instance } => self.instance_manager.stop(instance)?,
}
Expand All @@ -233,12 +259,18 @@ impl Desktop {
InstanceCommand::CreateView(info) => {
self.instance_manager.add_view(instance, &info);
self.presenter.present_view(instance, &info)?;

let focused = self.interaction.focused();
// If this instance is currently focused and the new view is primary, make it
// foreground so that the view is focused.
if self.interaction.focused_instance() == Some(instance)
//
// Ergonomics: instance() and band_location() are usually used together.
if focused.instance() == Some(instance)
&& let Some(band_location) = focused.band_location()
&& info.role == ViewRole::Primary
{
self.interaction.make_foreground(
band_location,
instance,
&self.instance_manager,
&mut self.presenter,
Expand Down
65 changes: 38 additions & 27 deletions desktop/src/desktop_interaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use massive_renderer::RenderGeometry;
use massive_shell::Scene;

use crate::{
desktop_presenter::{DesktopFocusPath, DesktopPresenter, DesktopTarget},
desktop_presenter::{BandLocation, DesktopFocusPath, DesktopPresenter, DesktopTarget},
event_router,
instance_manager::InstanceManager,
navigation::NavigationHitTester,
Expand All @@ -26,8 +26,11 @@ pub enum UserIntent {
// Architecture: Could just always Focus an explicit thing?
Focus(DesktopFocusPath),
StartInstance {
application: String,
originating_instance: InstanceId,
// Architecture: This should just a transformed rect. **BUT** it also determines the
// insertion index!!!
//
// Idea: What about looking for which presenter has the focus currently?
originating_instance: Option<InstanceId>,
},
StopInstance {
instance: InstanceId,
Expand All @@ -49,14 +52,18 @@ impl DesktopInteraction {
//
// Detail: This function assumes that the window is focused right now.
pub fn new(
path: DesktopFocusPath,
initial_focus: DesktopFocusPath,
instance_manager: &InstanceManager,
presenter: &mut DesktopPresenter,
scene: &Scene,
) -> Result<Self> {
let mut event_router = EventRouter::default();
let initial_transitions = event_router.focus(path);
presenter.forward_event_transitions(initial_transitions.transitions, instance_manager)?;
let initial_transitions = event_router.focus(initial_focus);
assert!(
presenter
.forward_event_transitions(initial_transitions.transitions, instance_manager)?
== UserIntent::None
);

// We can't call apply_changes yet as it needs a mutable presenter reference
// which we don't have. The transitions will be applied later.
Expand All @@ -71,8 +78,8 @@ impl DesktopInteraction {
})
}

pub fn focused_instance(&self) -> Option<InstanceId> {
self.event_router.focused().instance()
pub fn focused(&self) -> &DesktopFocusPath {
self.event_router.focused()
}

pub fn camera(&self) -> PixelCamera {
Expand All @@ -81,24 +88,32 @@ impl DesktopInteraction {

pub fn make_foreground(
&mut self,
band_location: BandLocation,
instance: InstanceId,
instance_manager: &InstanceManager,
presenter: &mut DesktopPresenter,
) -> Result<()> {
// If the window is not focus, we just focus the instance.
let primary_view = instance_manager.get_view_by_role(instance, ViewRole::Primary)?;
let focus_path = DesktopFocusPath::from_instance_and_view(instance, primary_view);
self.focus(focus_path, instance_manager, presenter)
let focus_path =
DesktopFocusPath::from_instance_and_view(band_location, instance, primary_view);
assert_eq!(
self.focus(focus_path, instance_manager, presenter)?,
UserIntent::None,
"Unexpected UserIntent in response to make_foreground"
);
Ok(())
}

pub fn focus(
&mut self,
focus_path: DesktopFocusPath,
instance_manager: &InstanceManager,
presenter: &mut DesktopPresenter,
) -> Result<()> {
) -> Result<UserIntent> {
let transitions = self.event_router.focus(focus_path);
presenter.forward_event_transitions(transitions.transitions, instance_manager)?;
let user_intent =
presenter.forward_event_transitions(transitions.transitions, instance_manager)?;

let camera = presenter.camera_for_focus(self.event_router.focused());
if let Some(camera) = camera {
Expand All @@ -109,7 +124,7 @@ impl DesktopInteraction {
);
}

Ok(())
Ok(user_intent)
}

pub fn process_input_event(
Expand All @@ -119,9 +134,9 @@ impl DesktopInteraction {
presenter: &mut DesktopPresenter,
render_geometry: &RenderGeometry,
) -> Result<UserIntent> {
let command = self.preprocess_keyboard_commands(event, instance_manager)?;
if command != UserIntent::None {
return Ok(command);
let intent = self.preprocess_keyboard_commands(event)?;
if intent != UserIntent::None {
return Ok(intent);
}

// Create a hit tester and forward events.
Expand All @@ -131,23 +146,21 @@ impl DesktopInteraction {
let hit_test = NavigationHitTester::new(navigation, render_geometry);
self.event_router.process(event, &hit_test)?
};
presenter.forward_event_transitions(transitions.transitions, instance_manager)?;
let intent =
presenter.forward_event_transitions(transitions.transitions, instance_manager)?;

// Robustness: Currently we don't check if the only the instance actually changed.
if let Some(new_focus) = transitions.focus_changed
&& let Some(instance) = new_focus.instance()
&& let Some(band_location) = new_focus.band_location()
{
self.make_foreground(instance, instance_manager, presenter)?;
self.make_foreground(band_location, instance, instance_manager, presenter)?;
};

Ok(UserIntent::None)
Ok(intent)
}

fn preprocess_keyboard_commands(
&self,
event: &Event<ViewEvent>,
instance_manager: &InstanceManager,
) -> Result<UserIntent> {
fn preprocess_keyboard_commands(&self, event: &Event<ViewEvent>) -> Result<UserIntent> {
// Catch Command+t and Command+w if a instance has the keyboard focus.

if let ViewEvent::KeyboardInput {
Expand All @@ -160,10 +173,8 @@ impl DesktopInteraction {
if let Some(instance) = self.event_router.focused().instance() {
match &key_event.logical_key {
Key::Character(c) if c.as_str() == "t" => {
let application = instance_manager.get_application_name(instance)?;
return Ok(UserIntent::StartInstance {
application: application.to_string(),
originating_instance: instance,
originating_instance: Some(instance),
});
}
Key::Character(c) if c.as_str() == "w" => {
Expand Down
Loading
Loading