mirror of
https://github.com/outbackdingo/hyprcosmic-comp.git
synced 2026-08-25 14:53:22 +00:00
Patch C: open a new window beside the focused one, not inside it
COSMIC's tiling tree is already N-ary -- `Data::Group` holds a `sizes: Vec<i32>` and `add_window` divides by `sizes.len() + 1`, so three panes in a row is perfectly representable. You just cannot get there by opening windows. `map_to_tree` always wraps the focused window in a *new* group whose orientation comes from that window's aspect ratio, so the second window splits the screen into two columns, and the third one -- opening into a column that is now taller than wide -- splits that column horizontally. Quadrants. The limit was the insertion rule, not the shape of the tree. `preserve_split` makes the new window join the group the focused window is already in, at the index right after it, keeping that group's direction. Three windows land as three panes in a row, which is what Hyprland's dwindle layout does with the option of the same name. The insert is `move_current_node`'s idiom, minus the move: children and `sizes` are positional, so the node goes in at `idx + 1` and `add_window(idx + 1)` puts the size at the matching slot. Get those out of step and every window in the group draws one slot over. Off by default, so an unconfigured build still lays out like COSMIC. HyprCosmic turns it on in cosmic.conf. It only affects where the next window is inserted, never existing windows, so the config watcher just pushes the flag down through `update_config` -- there is nothing to retile.
This commit is contained in:
@@ -83,6 +83,19 @@ pub struct CosmicCompConfig {
|
|||||||
/// If set to Global, autotile applies to all windows in all workspaces
|
/// If set to Global, autotile applies to all windows in all workspaces
|
||||||
/// If set to PerWorkspace, autotile only applies to new windows, and new workspaces
|
/// If set to PerWorkspace, autotile only applies to new windows, and new workspaces
|
||||||
pub autotile_behavior: TileBehavior,
|
pub autotile_behavior: TileBehavior,
|
||||||
|
/// Keep a group's split direction when a new window opens into it
|
||||||
|
///
|
||||||
|
/// Normally a new window *splits the focused window*: it is wrapped in a
|
||||||
|
/// fresh group whose direction is taken from that window's aspect ratio.
|
||||||
|
/// Two windows side by side are each taller than wide, so a third one
|
||||||
|
/// splits a column in half and you get quadrants -- three panes in a row
|
||||||
|
/// is not reachable by opening windows at all.
|
||||||
|
///
|
||||||
|
/// With this set the new window joins the group the focused window is
|
||||||
|
/// already in, keeping that group's direction, so three windows land as
|
||||||
|
/// three panes in a row. Named after Hyprland's `dwindle:preserve_split`,
|
||||||
|
/// which does the same thing.
|
||||||
|
pub preserve_split: bool,
|
||||||
/// Active hint enabled
|
/// Active hint enabled
|
||||||
pub active_hint: bool,
|
pub active_hint: bool,
|
||||||
/// Enables changing keyboard focus to windows when the cursor passes into them
|
/// Enables changing keyboard focus to windows when the cursor passes into them
|
||||||
@@ -129,6 +142,8 @@ impl Default for CosmicCompConfig {
|
|||||||
keyboard_config: Default::default(),
|
keyboard_config: Default::default(),
|
||||||
autotile: Default::default(),
|
autotile: Default::default(),
|
||||||
autotile_behavior: Default::default(),
|
autotile_behavior: Default::default(),
|
||||||
|
// Off, so the fork's default layout is still COSMIC's.
|
||||||
|
preserve_split: false,
|
||||||
active_hint: true,
|
active_hint: true,
|
||||||
focus_follows_cursor: false,
|
focus_follows_cursor: false,
|
||||||
cursor_follows_focus: false,
|
cursor_follows_focus: false,
|
||||||
|
|||||||
@@ -883,6 +883,16 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"preserve_split" => {
|
||||||
|
let new = get_config::<bool>(&config, "preserve_split");
|
||||||
|
if new != state.common.config.cosmic_conf.preserve_split {
|
||||||
|
state.common.config.cosmic_conf.preserve_split = new;
|
||||||
|
// Only affects where the *next* window is inserted, so
|
||||||
|
// there is nothing to retile -- `update_config` pushing the
|
||||||
|
// flag down to every `TilingLayout` is the whole job.
|
||||||
|
state.common.update_config();
|
||||||
|
}
|
||||||
|
}
|
||||||
"active_hint" => {
|
"active_hint" => {
|
||||||
let new = get_config::<bool>(&config, "active_hint");
|
let new = get_config::<bool>(&config, "active_hint");
|
||||||
if new != state.common.config.cosmic_conf.active_hint {
|
if new != state.common.config.cosmic_conf.active_hint {
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ pub struct TilingLayout {
|
|||||||
last_overview_hover: Option<(Option<Instant>, TargetZone)>,
|
last_overview_hover: Option<(Option<Instant>, TargetZone)>,
|
||||||
pub theme: cosmic::Theme,
|
pub theme: cosmic::Theme,
|
||||||
pub appearance: AppearanceConfig,
|
pub appearance: AppearanceConfig,
|
||||||
|
/// See `map_to_tree`, which is the only thing that reads it.
|
||||||
|
pub preserve_split: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
@@ -352,6 +354,7 @@ impl TilingLayout {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
theme: cosmic::Theme,
|
theme: cosmic::Theme,
|
||||||
appearance: AppearanceConfig,
|
appearance: AppearanceConfig,
|
||||||
|
preserve_split: bool,
|
||||||
output: &Output,
|
output: &Output,
|
||||||
) -> TilingLayout {
|
) -> TilingLayout {
|
||||||
TilingLayout {
|
TilingLayout {
|
||||||
@@ -369,6 +372,7 @@ impl TilingLayout {
|
|||||||
last_overview_hover: None,
|
last_overview_hover: None,
|
||||||
theme,
|
theme,
|
||||||
appearance,
|
appearance,
|
||||||
|
preserve_split,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,6 +434,7 @@ impl TilingLayout {
|
|||||||
last_active,
|
last_active,
|
||||||
direction,
|
direction,
|
||||||
minimize_rect,
|
minimize_rect,
|
||||||
|
self.preserve_split,
|
||||||
);
|
);
|
||||||
let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps);
|
let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps);
|
||||||
self.queue.push_tree(tree, duration, blocker);
|
self.queue.push_tree(tree, duration, blocker);
|
||||||
@@ -552,6 +557,7 @@ impl TilingLayout {
|
|||||||
node: Option<NodeId>,
|
node: Option<NodeId>,
|
||||||
direction: Option<Direction>,
|
direction: Option<Direction>,
|
||||||
minimize_rect: Option<Rectangle<i32, Local>>,
|
minimize_rect: Option<Rectangle<i32, Local>>,
|
||||||
|
preserve_split: bool,
|
||||||
) {
|
) {
|
||||||
let window = window.into();
|
let window = window.into();
|
||||||
let new_window = Node::new(Data::Mapped {
|
let new_window = Node::new(Data::Mapped {
|
||||||
@@ -582,17 +588,61 @@ impl TilingLayout {
|
|||||||
tree.insert(new_window, InsertBehavior::AsRoot).unwrap()
|
tree.insert(new_window, InsertBehavior::AsRoot).unwrap()
|
||||||
}
|
}
|
||||||
} else if let Some(ref node_id) = node {
|
} else if let Some(ref node_id) = node {
|
||||||
let orientation = {
|
// Join the focused window's group, or split the focused window.
|
||||||
let window_size = tree.get(node_id).unwrap().data().geometry().size;
|
//
|
||||||
if window_size.w > window_size.h {
|
// Splitting is the normal behaviour and the reason a group with
|
||||||
Orientation::Vertical
|
// three children is unreachable by opening windows: the new window
|
||||||
} else {
|
// is wrapped together with the focused one in a *fresh* group whose
|
||||||
Orientation::Horizontal
|
// orientation comes from the focused window's aspect ratio. Two
|
||||||
}
|
// windows side by side are each taller than wide, so the third
|
||||||
};
|
// splits a column horizontally and you get quadrants, forever.
|
||||||
let new_id = tree.insert(new_window, InsertBehavior::AsRoot).unwrap();
|
//
|
||||||
TilingLayout::new_group(tree, node_id, &new_id, orientation).unwrap();
|
// Preserving the split appends into the group the focused window is
|
||||||
new_id
|
// already in and keeps that group's orientation, which the sizes
|
||||||
|
// vector has always been able to represent -- `add_window` divides
|
||||||
|
// by `len + 1`, not by two. Three windows then land as three panes
|
||||||
|
// in a row.
|
||||||
|
//
|
||||||
|
// The parent of a mapped node is always a group; `is_group` is
|
||||||
|
// checked anyway because getting it wrong would panic in
|
||||||
|
// `add_window` rather than misplace a window.
|
||||||
|
let join = preserve_split
|
||||||
|
.then(|| tree.get(node_id).unwrap().parent().cloned())
|
||||||
|
.flatten()
|
||||||
|
.filter(|parent_id| tree.get(parent_id).unwrap().data().is_group());
|
||||||
|
|
||||||
|
if let Some(parent_id) = join {
|
||||||
|
let idx = tree
|
||||||
|
.children_ids(&parent_id)
|
||||||
|
.unwrap()
|
||||||
|
.position(|id| id == node_id)
|
||||||
|
.unwrap();
|
||||||
|
let new_id = tree
|
||||||
|
.insert(new_window, InsertBehavior::UnderNode(&parent_id))
|
||||||
|
.unwrap();
|
||||||
|
// Directly after the focused window, and the size goes in at
|
||||||
|
// the same index -- children and `sizes` are positional, so the
|
||||||
|
// two must agree or every window in the group is drawn one
|
||||||
|
// slot over.
|
||||||
|
tree.make_nth_sibling(&new_id, idx + 1).unwrap();
|
||||||
|
tree.get_mut(&parent_id)
|
||||||
|
.unwrap()
|
||||||
|
.data_mut()
|
||||||
|
.add_window(idx + 1);
|
||||||
|
new_id
|
||||||
|
} else {
|
||||||
|
let orientation = {
|
||||||
|
let window_size = tree.get(node_id).unwrap().data().geometry().size;
|
||||||
|
if window_size.w > window_size.h {
|
||||||
|
Orientation::Vertical
|
||||||
|
} else {
|
||||||
|
Orientation::Horizontal
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let new_id = tree.insert(new_window, InsertBehavior::AsRoot).unwrap();
|
||||||
|
TilingLayout::new_group(tree, node_id, &new_id, orientation).unwrap();
|
||||||
|
new_id
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// nothing? then we add to the root
|
// nothing? then we add to the root
|
||||||
if let Some(root_id) = tree.root_node_id().cloned() {
|
if let Some(root_id) = tree.root_node_id().cloned() {
|
||||||
@@ -2204,6 +2254,7 @@ impl TilingLayout {
|
|||||||
Some(current_node),
|
Some(current_node),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
self.preserve_split,
|
||||||
);
|
);
|
||||||
|
|
||||||
let node = window.tiling_node_id.lock().unwrap().clone().unwrap();
|
let node = window.tiling_node_id.lock().unwrap().clone().unwrap();
|
||||||
@@ -2794,6 +2845,7 @@ impl TilingLayout {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
self.preserve_split,
|
||||||
);
|
);
|
||||||
window
|
window
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ pub struct WorkspaceSet {
|
|||||||
output: Output,
|
output: Output,
|
||||||
theme: cosmic::Theme,
|
theme: cosmic::Theme,
|
||||||
appearance: AppearanceConfig,
|
appearance: AppearanceConfig,
|
||||||
|
preserve_split: bool,
|
||||||
pub sticky_layer: FloatingLayout,
|
pub sticky_layer: FloatingLayout,
|
||||||
pub minimized_windows: Vec<MinimizedWindow>,
|
pub minimized_windows: Vec<MinimizedWindow>,
|
||||||
pub workspaces: Vec<Workspace>,
|
pub workspaces: Vec<Workspace>,
|
||||||
@@ -379,6 +380,7 @@ fn create_workspace(
|
|||||||
tiling: bool,
|
tiling: bool,
|
||||||
theme: cosmic::Theme,
|
theme: cosmic::Theme,
|
||||||
appearance: AppearanceConfig,
|
appearance: AppearanceConfig,
|
||||||
|
preserve_split: bool,
|
||||||
) -> Workspace {
|
) -> Workspace {
|
||||||
let workspace_handle = state
|
let workspace_handle = state
|
||||||
.create_workspace(
|
.create_workspace(
|
||||||
@@ -408,6 +410,7 @@ fn create_workspace(
|
|||||||
tiling,
|
tiling,
|
||||||
theme.clone(),
|
theme.clone(),
|
||||||
appearance,
|
appearance,
|
||||||
|
preserve_split,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,6 +422,7 @@ fn create_workspace_from_pinned(
|
|||||||
active: bool,
|
active: bool,
|
||||||
theme: cosmic::Theme,
|
theme: cosmic::Theme,
|
||||||
appearance: AppearanceConfig,
|
appearance: AppearanceConfig,
|
||||||
|
preserve_split: bool,
|
||||||
) -> Workspace {
|
) -> Workspace {
|
||||||
let workspace_handle = state
|
let workspace_handle = state
|
||||||
.create_workspace(
|
.create_workspace(
|
||||||
@@ -453,6 +457,7 @@ fn create_workspace_from_pinned(
|
|||||||
output.clone(),
|
output.clone(),
|
||||||
theme.clone(),
|
theme.clone(),
|
||||||
appearance,
|
appearance,
|
||||||
|
preserve_split,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,6 +494,7 @@ impl WorkspaceSet {
|
|||||||
tiling_enabled: bool,
|
tiling_enabled: bool,
|
||||||
theme: &cosmic::Theme,
|
theme: &cosmic::Theme,
|
||||||
appearance: AppearanceConfig,
|
appearance: AppearanceConfig,
|
||||||
|
preserve_split: bool,
|
||||||
) -> WorkspaceSet {
|
) -> WorkspaceSet {
|
||||||
let group_handle = state.create_workspace_group();
|
let group_handle = state.create_workspace_group();
|
||||||
let sticky_layer = FloatingLayout::new(theme.clone(), appearance, output);
|
let sticky_layer = FloatingLayout::new(theme.clone(), appearance, output);
|
||||||
@@ -504,6 +510,7 @@ impl WorkspaceSet {
|
|||||||
workspaces: Vec::new(),
|
workspaces: Vec::new(),
|
||||||
output: output.clone(),
|
output: output.clone(),
|
||||||
appearance,
|
appearance,
|
||||||
|
preserve_split,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,6 +629,7 @@ impl WorkspaceSet {
|
|||||||
self.tiling_enabled,
|
self.tiling_enabled,
|
||||||
self.theme.clone(),
|
self.theme.clone(),
|
||||||
self.appearance,
|
self.appearance,
|
||||||
|
self.preserve_split,
|
||||||
);
|
);
|
||||||
workspace_set_idx(
|
workspace_set_idx(
|
||||||
state,
|
state,
|
||||||
@@ -827,6 +835,7 @@ pub struct Workspaces {
|
|||||||
mode: WorkspaceMode,
|
mode: WorkspaceMode,
|
||||||
autotile: bool,
|
autotile: bool,
|
||||||
autotile_behavior: TileBehavior,
|
autotile_behavior: TileBehavior,
|
||||||
|
preserve_split: bool,
|
||||||
theme: cosmic::Theme,
|
theme: cosmic::Theme,
|
||||||
appearance: AppearanceConfig,
|
appearance: AppearanceConfig,
|
||||||
// Persisted workspace to add on first `output_add`
|
// Persisted workspace to add on first `output_add`
|
||||||
@@ -842,6 +851,7 @@ impl Workspaces {
|
|||||||
mode: config.cosmic_conf.workspaces.workspace_mode,
|
mode: config.cosmic_conf.workspaces.workspace_mode,
|
||||||
autotile: config.cosmic_conf.autotile,
|
autotile: config.cosmic_conf.autotile,
|
||||||
autotile_behavior: config.cosmic_conf.autotile_behavior,
|
autotile_behavior: config.cosmic_conf.autotile_behavior,
|
||||||
|
preserve_split: config.cosmic_conf.preserve_split,
|
||||||
theme,
|
theme,
|
||||||
appearance: config.cosmic_conf.appearance_settings,
|
appearance: config.cosmic_conf.appearance_settings,
|
||||||
persisted_workspaces: config.cosmic_conf.pinned_workspaces.clone(),
|
persisted_workspaces: config.cosmic_conf.pinned_workspaces.clone(),
|
||||||
@@ -871,6 +881,7 @@ impl Workspaces {
|
|||||||
self.autotile,
|
self.autotile,
|
||||||
&self.theme,
|
&self.theme,
|
||||||
self.appearance,
|
self.appearance,
|
||||||
|
self.preserve_split,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
workspace_state.add_group_output(&set.group, output);
|
workspace_state.add_group_output(&set.group, output);
|
||||||
@@ -885,6 +896,7 @@ impl Workspaces {
|
|||||||
false,
|
false,
|
||||||
self.theme.clone(),
|
self.theme.clone(),
|
||||||
self.appearance,
|
self.appearance,
|
||||||
|
self.preserve_split,
|
||||||
);
|
);
|
||||||
set.workspaces.push(workspace);
|
set.workspaces.push(workspace);
|
||||||
}
|
}
|
||||||
@@ -1163,13 +1175,16 @@ impl Workspaces {
|
|||||||
self.mode = config.cosmic_conf.workspaces.workspace_mode;
|
self.mode = config.cosmic_conf.workspaces.workspace_mode;
|
||||||
self.layout = config.cosmic_conf.workspaces.workspace_layout;
|
self.layout = config.cosmic_conf.workspaces.workspace_layout;
|
||||||
self.appearance = config.cosmic_conf.appearance_settings;
|
self.appearance = config.cosmic_conf.appearance_settings;
|
||||||
|
self.preserve_split = config.cosmic_conf.preserve_split;
|
||||||
|
|
||||||
for set in self.sets.values_mut() {
|
for set in self.sets.values_mut() {
|
||||||
set.appearance = self.appearance;
|
set.appearance = self.appearance;
|
||||||
set.sticky_layer.appearance = self.appearance;
|
set.sticky_layer.appearance = self.appearance;
|
||||||
|
set.preserve_split = self.preserve_split;
|
||||||
for workspace in set.workspaces.iter_mut() {
|
for workspace in set.workspaces.iter_mut() {
|
||||||
workspace.floating_layer.appearance = self.appearance;
|
workspace.floating_layer.appearance = self.appearance;
|
||||||
workspace.tiling_layer.appearance = self.appearance;
|
workspace.tiling_layer.appearance = self.appearance;
|
||||||
|
workspace.tiling_layer.preserve_split = self.preserve_split;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1219,6 +1234,7 @@ impl Workspaces {
|
|||||||
config.cosmic_conf.autotile,
|
config.cosmic_conf.autotile,
|
||||||
self.theme.clone(),
|
self.theme.clone(),
|
||||||
self.appearance,
|
self.appearance,
|
||||||
|
self.preserve_split,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -387,8 +387,9 @@ impl Workspace {
|
|||||||
tiling_enabled: bool,
|
tiling_enabled: bool,
|
||||||
theme: cosmic::Theme,
|
theme: cosmic::Theme,
|
||||||
appearance: AppearanceConfig,
|
appearance: AppearanceConfig,
|
||||||
|
preserve_split: bool,
|
||||||
) -> Workspace {
|
) -> Workspace {
|
||||||
let tiling_layer = TilingLayout::new(theme.clone(), appearance, &output);
|
let tiling_layer = TilingLayout::new(theme.clone(), appearance, preserve_split, &output);
|
||||||
let floating_layer = FloatingLayout::new(theme, appearance, &output);
|
let floating_layer = FloatingLayout::new(theme, appearance, &output);
|
||||||
let output_match = output_match_for_output(&output);
|
let output_match = output_match_for_output(&output);
|
||||||
|
|
||||||
@@ -421,8 +422,9 @@ impl Workspace {
|
|||||||
output: Output,
|
output: Output,
|
||||||
theme: cosmic::Theme,
|
theme: cosmic::Theme,
|
||||||
appearance: AppearanceConfig,
|
appearance: AppearanceConfig,
|
||||||
|
preserve_split: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let tiling_layer = TilingLayout::new(theme.clone(), appearance, &output);
|
let tiling_layer = TilingLayout::new(theme.clone(), appearance, preserve_split, &output);
|
||||||
let floating_layer = FloatingLayout::new(theme, appearance, &output);
|
let floating_layer = FloatingLayout::new(theme, appearance, &output);
|
||||||
let output_match = output_match_for_output(&output);
|
let output_match = output_match_for_output(&output);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user