commit 9471ef55526f246a02fa1a2fe6ec49ad08f063f0
parent 6a77cc476d48ddd97f226a440e019c1e24762f01
Author: deurzen <max@deurzen.net>
Date: Fri, 27 May 2022 10:45:36 +0200
adds focus and state manipulation bindings
Diffstat:
9 files changed, 1193 insertions(+), 23 deletions(-)
diff --git a/include/kranewl/input/keybindings.hh b/include/kranewl/input/keybindings.hh
@@ -32,22 +32,155 @@ static const KeyBindings key_bindings = {
{ { XKB_KEY_Q, MODKEY | CTRL | SHIFT },
CALL(exit())
},
+
+// view state modifiers
+{ { XKB_KEY_c, MODKEY },
+ CALL(kill_focus())
+},
+{ { XKB_KEY_space, MODKEY | SHIFT },
+ CALL(set_floating_focus(Toggle::Reverse))
+},
+{ { XKB_KEY_f, MODKEY },
+ CALL(set_fullscreen_focus(Toggle::Reverse))
+},
+{ { XKB_KEY_x, MODKEY },
+ CALL(set_sticky_focus(Toggle::Reverse))
+},
+{ { XKB_KEY_f, MODKEY | SECKEY | CTRL },
+ CALL(set_contained_focus(Toggle::Reverse))
+},
+{ { XKB_KEY_i, MODKEY | SECKEY | CTRL },
+ CALL(set_invincible_focus(Toggle::Reverse))
+},
+{ { XKB_KEY_y, MODKEY },
+ CALL(set_iconify_focus(Toggle::Reverse))
+},
+{ { XKB_KEY_u, MODKEY },
+ CALL(pop_deiconify())
+},
+{ { XKB_KEY_u, MODKEY | SECKEY },
+ CALL(deiconify_all())
+},
+
+// view arrangers
+{ { XKB_KEY_space, MODKEY | CTRL },
+ CALL(center_focus())
+},
+{ { XKB_KEY_h, MODKEY | CTRL },
+ [](Model& model) {
+ View_ptr focus = model.focused_view();
+
+ if (focus && model.is_free(focus))
+ model.nudge_focus(Edge::Left, 15);
+ else
+ model.shuffle_main(Direction::Backward);
+ }
+},
+{ { XKB_KEY_j, MODKEY | CTRL },
+ [](Model& model) {
+ View_ptr focus = model.focused_view();
+
+ if (focus && model.is_free(focus))
+ model.nudge_focus(Edge::Bottom, 15);
+ else
+ model.shuffle_stack(Direction::Forward);
+ }
+},
+{ { XKB_KEY_k, MODKEY | CTRL },
+ [](Model& model) {
+ View_ptr focus = model.focused_view();
+
+ if (focus && model.is_free(focus))
+ model.nudge_focus(Edge::Top, 15);
+ else
+ model.shuffle_stack(Direction::Backward);
+ }
+},
+{ { XKB_KEY_l, MODKEY | CTRL },
+ [](Model& model) {
+ View_ptr focus = model.focused_view();
+
+ if (focus && model.is_free(focus))
+ model.nudge_focus(Edge::Right, 15);
+ else
+ model.shuffle_main(Direction::Forward);
+ }
+},
+{ { XKB_KEY_H, MODKEY | CTRL | SHIFT },
+ CALL(stretch_focus(Edge::Left, 15))
+},
+{ { XKB_KEY_J, MODKEY | CTRL | SHIFT },
+ CALL(stretch_focus(Edge::Bottom, 15))
+},
+{ { XKB_KEY_K, MODKEY | CTRL | SHIFT },
+ CALL(stretch_focus(Edge::Top, 15))
+},
+{ { XKB_KEY_L, MODKEY | CTRL | SHIFT },
+ CALL(stretch_focus(Edge::Right, 15))
+},
+{ { XKB_KEY_Y, MODKEY | CTRL | SHIFT },
+ CALL(stretch_focus(Edge::Left, -15))
+},
+{ { XKB_KEY_U, MODKEY | CTRL | SHIFT },
+ CALL(stretch_focus(Edge::Bottom, -15))
+},
+{ { XKB_KEY_I, MODKEY | CTRL | SHIFT },
+ CALL(stretch_focus(Edge::Top, -15))
+},
+{ { XKB_KEY_O, MODKEY | CTRL | SHIFT },
+ CALL(stretch_focus(Edge::Right, -15))
+},
+{ { XKB_KEY_leftarrow, MODKEY | CTRL },
+ CALL(snap_focus(Edge::Left))
+},
+{ { XKB_KEY_downarrow, MODKEY | CTRL },
+ CALL(snap_focus(Edge::Bottom))
+},
+{ { XKB_KEY_uparrow, MODKEY | CTRL },
+ CALL(snap_focus(Edge::Top))
+},
+{ { XKB_KEY_rightarrow, MODKEY | CTRL },
+ CALL(snap_focus(Edge::Right))
+},
+{ { XKB_KEY_j, MODKEY },
+ CALL(cycle_focus(Direction::Forward))
+},
+{ { XKB_KEY_k, MODKEY },
+ CALL(cycle_focus(Direction::Backward))
+},
+{ { XKB_KEY_J, MODKEY | SHIFT },
+ CALL(drag_focus(Direction::Forward))
+},
+{ { XKB_KEY_K, MODKEY | SHIFT },
+ CALL(drag_focus(Direction::Backward))
+},
+{ { XKB_KEY_r, MODKEY },
+ CALL(reverse_views())
+},
+{ { XKB_KEY_semicolon, MODKEY | SHIFT },
+ CALL(rotate_views(Direction::Forward))
+},
+{ { XKB_KEY_comma, MODKEY | SHIFT },
+ CALL(rotate_views(Direction::Backward))
+},
+
+// workspace layout modifiers
{ { XKB_KEY_F, MODKEY | SHIFT },
CALL(set_layout(LayoutHandler::LayoutKind::Float))
},
{ { XKB_KEY_L, MODKEY | SHIFT },
CALL(set_layout(LayoutHandler::LayoutKind::FramelessFloat))
},
-{ { XKB_KEY_Z, MODKEY },
+{ { XKB_KEY_z, MODKEY },
CALL(set_layout(LayoutHandler::LayoutKind::SingleFloat))
},
{ { XKB_KEY_Z, MODKEY | SHIFT },
CALL(set_layout(LayoutHandler::LayoutKind::FramelessSingleFloat))
},
-{ { XKB_KEY_M, MODKEY },
+{ { XKB_KEY_m, MODKEY },
CALL(set_layout(LayoutHandler::LayoutKind::Monocle))
},
-{ { XKB_KEY_D, MODKEY | CTRL },
+{ { XKB_KEY_d, MODKEY | CTRL },
CALL(set_layout(LayoutHandler::LayoutKind::MainDeck))
},
{ { XKB_KEY_D, MODKEY | SHIFT },
@@ -56,10 +189,10 @@ static const KeyBindings key_bindings = {
{ { XKB_KEY_D, MODKEY | CTRL | SHIFT },
CALL(set_layout(LayoutHandler::LayoutKind::DoubleDeck))
},
-{ { XKB_KEY_G, MODKEY },
+{ { XKB_KEY_g, MODKEY },
CALL(set_layout(LayoutHandler::LayoutKind::Center))
},
-{ { XKB_KEY_T, MODKEY },
+{ { XKB_KEY_t, MODKEY },
CALL(set_layout(LayoutHandler::LayoutKind::DoubleStack))
},
{ { XKB_KEY_T, MODKEY | SHIFT },
@@ -74,7 +207,7 @@ static const KeyBindings key_bindings = {
{ { XKB_KEY_Y, MODKEY | SHIFT },
CALL(set_layout(LayoutHandler::LayoutKind::HorizontalStack))
},
-{ { XKB_KEY_Y, MODKEY | CTRL },
+{ { XKB_KEY_y, MODKEY | CTRL },
CALL(set_layout(LayoutHandler::LayoutKind::CompactHorizontalStack))
},
{ { XKB_KEY_V, MODKEY | SHIFT },
diff --git a/include/kranewl/model.hh b/include/kranewl/model.hh
@@ -37,6 +37,8 @@ public:
void register_server(Server_ptr);
void exit();
+ const View_ptr focused_view() const;
+
KeyBindings const& key_bindings() const;
Output_ptr create_output(struct wlr_output*, struct wlr_scene_output*, Region const&&);
@@ -61,6 +63,13 @@ public:
void place_view(Placement&);
void sync_focus();
+ void cycle_focus(Direction);
+ void drag_focus(Direction);
+
+ void reverse_views();
+ void rotate_views(Direction);
+ void shuffle_main(Direction);
+ void shuffle_stack(Direction);
void move_view_to_workspace(View_ptr, Index);
void move_view_to_workspace(View_ptr, Workspace_ptr);
@@ -81,9 +90,57 @@ public:
void set_layout(LayoutHandler::LayoutKind);
void set_layout_retain_region(LayoutHandler::LayoutKind);
+ void toggle_layout_data();
+ void cycle_layout_data(Direction);
+ void copy_data_from_prev_layout();
+
+ void change_gap_size(Util::Change<int>);
+ void change_main_count(Util::Change<int>);
+ void change_main_factor(Util::Change<float>);
+ void change_margin(Util::Change<int>);
+ void change_margin(Edge, Util::Change<int>);
+ void reset_gap_size();
+ void reset_margin();
+ void reset_layout_data();
+
+ void save_layout(std::size_t) const;
+ void load_layout(std::size_t);
+
void apply_layout(Index);
void apply_layout(Workspace_ptr);
+ void kill_focus();
+ void kill_view(View_ptr);
+
+ void set_floating_focus(Toggle);
+ void set_floating_view(Toggle, View_ptr);
+ void set_fullscreen_focus(Toggle);
+ void set_fullscreen_view(Toggle, View_ptr);
+ void set_sticky_focus(Toggle);
+ void set_sticky_view(Toggle, View_ptr);
+ void set_contained_focus(Toggle);
+ void set_contained_view(Toggle, View_ptr);
+ void set_invincible_focus(Toggle);
+ void set_invincible_view(Toggle, View_ptr);
+ void set_iconifyable_focus(Toggle);
+ void set_iconifyable_view(Toggle, View_ptr);
+ void set_iconify_focus(Toggle);
+ void set_iconify_view(Toggle, View_ptr);
+
+ void center_focus();
+ void center_view(View_ptr);
+ void nudge_focus(Edge, Util::Change<std::size_t>);
+ void nudge_view(Edge, Util::Change<std::size_t>, View_ptr);
+ void stretch_focus(Edge, Util::Change<int>);
+ void stretch_view(Edge, Util::Change<int>, View_ptr);
+ void inflate_focus(Util::Change<int>);
+ void inflate_view(Util::Change<int>, View_ptr);
+ void snap_focus(Edge);
+ void snap_view(Edge, View_ptr);
+
+ void pop_deiconify();
+ void deiconify_all();
+
bool is_free(View_ptr) const;
void spawn_external(std::string&&) const;
diff --git a/include/kranewl/tree/client.hh b/include/kranewl/tree/client.hh
@@ -122,6 +122,7 @@ typedef struct Client final {
bool m_attaching;
std::chrono::time_point<std::chrono::steady_clock> m_last_focused;
+ std::chrono::time_point<std::chrono::steady_clock> m_last_touched;
std::chrono::time_point<std::chrono::steady_clock> m_managed_since;
struct wl_listener ml_commit;
diff --git a/include/kranewl/tree/view.hh b/include/kranewl/tree/view.hh
@@ -71,6 +71,8 @@ typedef struct View {
void map(struct wlr_surface*, bool, struct wlr_output*, bool);
void unmap();
+ void touch();
+
virtual void focus(bool) = 0;
virtual void moveresize(Region const&, Extents const&, bool) = 0;
@@ -143,9 +145,10 @@ typedef struct View {
bool m_disowned;
std::chrono::time_point<std::chrono::steady_clock> m_last_focused;
+ std::chrono::time_point<std::chrono::steady_clock> m_last_touched;
std::chrono::time_point<std::chrono::steady_clock> m_managed_since;
-private:
+protected:
void set_inner_region(Region const&);
void set_active_region(Region const&);
diff --git a/src/kranewl/model.cc b/src/kranewl/model.cc
@@ -18,6 +18,7 @@
#include <spdlog/spdlog.h>
+#include <algorithm>
#include <iomanip>
#include <optional>
@@ -119,6 +120,12 @@ Model::exit()
mp_server->terminate();
}
+const View_ptr
+Model::focused_view() const
+{
+ return mp_focus;
+}
+
KeyBindings const&
Model::key_bindings() const
{
@@ -310,6 +317,8 @@ Model::place_view(Placement& placement)
void
Model::sync_focus()
{
+ TRACE();
+
View_ptr active = mp_workspace->active();
if (active && active != mp_focus)
@@ -321,6 +330,136 @@ Model::sync_focus()
}
void
+Model::cycle_focus(Direction direction)
+{
+ TRACE();
+
+ if (mp_workspace->size() <= 1)
+ return;
+
+ mp_workspace->cycle(direction);
+ sync_focus();
+}
+
+void
+Model::drag_focus(Direction direction)
+{
+ TRACE();
+
+ if (mp_workspace->size() <= 1)
+ return;
+
+ mp_workspace->drag(direction);
+ sync_focus();
+
+ apply_layout(mp_workspace);
+}
+
+void
+Model::reverse_views()
+{
+ TRACE();
+
+ if (mp_workspace->size() <= 1)
+ return;
+
+ mp_workspace->reverse();
+ mp_workspace->active()->focus(true);
+ sync_focus();
+
+ apply_layout(mp_workspace);
+}
+
+void
+Model::rotate_views(Direction direction)
+{
+ TRACE();
+
+ if (mp_workspace->size() <= 1)
+ return;
+
+ mp_workspace->rotate(direction);
+ mp_workspace->active()->focus(true);
+ sync_focus();
+
+ apply_layout(mp_workspace);
+}
+
+void
+Model::shuffle_main(Direction direction)
+{
+ TRACE();
+
+ std::size_t main_count
+ = std::min(static_cast<std::size_t>(mp_workspace->main_count()), mp_workspace->size());
+
+ if (main_count <= 1)
+ return;
+
+ Index focus_index = *mp_workspace->views().index();
+ std::optional<Index> last_touched_index = std::nullopt;
+
+ if (focus_index >= main_count) {
+ View_ptr last_touched = *std::max_element(
+ mp_workspace->begin(),
+ mp_workspace->begin() + main_count,
+ [](View_ptr view1, View_ptr view2) -> bool {
+ return view1->m_last_touched < view2->m_last_touched;
+ }
+ );
+
+ last_touched_index
+ = mp_workspace->views().index_of_element(last_touched);
+ }
+
+ mp_workspace->shuffle_main(direction);
+ mp_workspace->active()->focus(true);
+ sync_focus();
+
+ if (last_touched_index)
+ (*mp_workspace)[*last_touched_index]->touch();
+
+ apply_layout(mp_workspace);
+}
+
+void
+Model::shuffle_stack(Direction direction)
+{
+ TRACE();
+
+ std::size_t main_count
+ = std::min(static_cast<std::size_t>(mp_workspace->main_count()), mp_workspace->size());
+
+ if ((mp_workspace->size() - main_count) <= 1)
+ return;
+
+ Index focus_index = *mp_workspace->views().index();
+ std::optional<Index> last_touched_index = std::nullopt;
+
+ if (focus_index < main_count) {
+ View_ptr last_touched = *std::max_element(
+ mp_workspace->begin() + main_count,
+ mp_workspace->end(),
+ [](View_ptr view1, View_ptr view2) -> bool {
+ return view1->m_last_touched < view2->m_last_touched;
+ }
+ );
+
+ last_touched_index
+ = mp_workspace->views().index_of_element(last_touched);
+ }
+
+ mp_workspace->shuffle_stack(direction);
+ mp_workspace->active()->focus(true);
+ sync_focus();
+
+ if (last_touched_index)
+ (*mp_workspace)[*last_touched_index]->touch();
+
+ apply_layout(mp_workspace);
+}
+
+void
Model::move_view_to_workspace(View_ptr view, Index index)
{
TRACE();
@@ -529,6 +668,8 @@ Model::set_layout(LayoutHandler::LayoutKind layout)
void
Model::set_layout_retain_region(LayoutHandler::LayoutKind layout)
{
+ TRACE();
+
Cycle<View_ptr> const& views = mp_workspace->views();
std::vector<Region> regions;
@@ -560,6 +701,122 @@ Model::set_layout_retain_region(LayoutHandler::LayoutKind layout)
}
void
+Model::toggle_layout_data()
+{
+ TRACE();
+
+ mp_workspace->toggle_layout_data();
+ apply_layout(mp_workspace);
+}
+
+void
+Model::cycle_layout_data(Direction direction)
+{
+ TRACE();
+
+ mp_workspace->cycle_layout_data(direction);
+ apply_layout(mp_workspace);
+}
+
+void
+Model::copy_data_from_prev_layout()
+{
+ TRACE();
+
+ mp_workspace->copy_data_from_prev_layout();
+ apply_layout(mp_workspace);
+}
+
+void
+Model::change_gap_size(Util::Change<int> change)
+{
+ TRACE();
+
+ mp_workspace->change_gap_size(change);
+ apply_layout(mp_workspace);
+}
+
+void
+Model::change_main_count(Util::Change<int> change)
+{
+ TRACE();
+
+ mp_workspace->change_main_count(change);
+ apply_layout(mp_workspace);
+}
+
+void
+Model::change_main_factor(Util::Change<float> change)
+{
+ TRACE();
+
+ mp_workspace->change_main_factor(change);
+ apply_layout(mp_workspace);
+}
+
+void
+Model::change_margin(Util::Change<int> change)
+{
+ TRACE();
+
+ mp_workspace->change_margin(change);
+ apply_layout(mp_workspace);
+}
+
+void
+Model::change_margin(Edge edge, Util::Change<int> change)
+{
+ TRACE();
+
+ mp_workspace->change_margin(edge, change);
+ apply_layout(mp_workspace);
+}
+
+void
+Model::reset_gap_size()
+{
+ TRACE();
+
+ mp_workspace->reset_gap_size();
+ apply_layout(mp_workspace);
+}
+
+void
+Model::reset_margin()
+{
+ TRACE();
+
+ mp_workspace->reset_margin();
+ apply_layout(mp_workspace);
+}
+
+void
+Model::reset_layout_data()
+{
+ TRACE();
+
+ mp_workspace->reset_layout_data();
+ apply_layout(mp_workspace);
+}
+
+void
+Model::save_layout(std::size_t number) const
+{
+ TRACE();
+
+ mp_workspace->save_layout(number);
+}
+
+void
+Model::load_layout(std::size_t number)
+{
+ TRACE();
+
+ mp_workspace->load_layout(number);
+ apply_layout(mp_workspace);
+}
+
+void
Model::apply_layout(Index index)
{
TRACE();
@@ -583,6 +840,702 @@ Model::apply_layout(Workspace_ptr workspace)
place_view(placement);
}
+void
+Model::kill_focus()
+{
+ TRACE();
+
+ if (mp_focus)
+ kill_view(mp_focus);
+}
+
+void
+Model::kill_view(View_ptr view)
+{
+ TRACE();
+
+ if (!view->m_invincible) {
+ // TODO: kill view
+ }
+}
+
+void
+Model::set_floating_focus(Toggle toggle)
+{
+ TRACE();
+
+ if (mp_focus)
+ set_floating_view(toggle, mp_focus);
+}
+
+void
+Model::set_floating_view(Toggle toggle, View_ptr view)
+{
+ TRACE();
+
+ switch (toggle) {
+ case Toggle::On: view->m_floating = true; break;
+ case Toggle::Off: view->m_floating = false; break;
+ case Toggle::Reverse: view->m_floating = !view->m_floating; break;
+ default: return;
+ }
+
+ apply_layout(view->mp_workspace);
+}
+
+void
+Model::set_fullscreen_focus(Toggle toggle)
+{
+ TRACE();
+
+ if (mp_focus)
+ set_fullscreen_view(toggle, mp_focus);
+}
+
+void
+Model::set_fullscreen_view(Toggle toggle, View_ptr view)
+{
+ TRACE();
+
+ switch (toggle) {
+ case Toggle::On:
+ {
+ if (view->m_fullscreen)
+ return;
+
+ view->m_fullscreen = true;
+
+ // TODO: set fullscreen state
+
+ Workspace_ptr workspace = view->mp_workspace;
+ apply_layout(workspace);
+
+ m_fullscreen_map[view] = view->m_free_region;
+
+ return;
+ }
+ case Toggle::Off:
+ {
+ if (!view->m_fullscreen)
+ return;
+
+ if (!view->m_contained)
+ view->set_free_region(m_fullscreen_map.at(view));
+
+ view->m_fullscreen = false;
+
+ // TODO: unset fullscreen state
+
+ Workspace_ptr workspace = view->mp_workspace;
+ apply_layout(workspace);
+
+ m_fullscreen_map.erase(view);
+
+ return;
+ }
+ case Toggle::Reverse:
+ {
+ set_fullscreen_view(
+ view->m_fullscreen
+ ? Toggle::Off
+ : Toggle::On,
+ view
+ );
+
+ return;
+ }
+ default: return;
+ }
+}
+
+void
+Model::set_sticky_focus(Toggle toggle)
+{
+ TRACE();
+
+ if (mp_focus)
+ set_sticky_view(toggle, mp_focus);
+}
+
+void
+Model::set_sticky_view(Toggle toggle, View_ptr view)
+{
+ TRACE();
+
+ switch (toggle) {
+ case Toggle::On:
+ {
+ if (view->m_sticky)
+ return;
+
+ if (view->m_iconified)
+ set_iconify_view(Toggle::Off, view);
+
+ std::for_each(
+ m_workspaces.begin(),
+ m_workspaces.end(),
+ [view](Workspace_ptr workspace) {
+ if (workspace != view->mp_workspace)
+ workspace->add_view(view);
+ }
+ );
+
+ // TODO: set sticky state
+
+ Workspace_ptr workspace = view->mp_workspace;
+
+ // TODO: view->stick();
+ apply_layout(workspace);
+
+ return;
+ }
+ case Toggle::Off:
+ {
+ if (!view->m_sticky)
+ return;
+
+ std::for_each(
+ m_workspaces.begin(),
+ m_workspaces.end(),
+ [=,this](Workspace_ptr workspace) {
+ if (workspace != mp_workspace) {
+ workspace->remove_view(view);
+ workspace->remove_icon(view);
+ workspace->remove_disowned(view);
+ } else
+ view->mp_workspace = workspace;
+ }
+ );
+
+ // TODO: unset sticky state
+
+ // TODO: view->unstick();
+ apply_layout(mp_workspace);
+
+ return;
+ }
+ case Toggle::Reverse:
+ {
+ set_sticky_view(
+ view->m_sticky
+ ? Toggle::Off
+ : Toggle::On,
+ view
+ );
+
+ return;
+ }
+ default: return;
+ }
+}
+
+void
+Model::set_contained_focus(Toggle toggle)
+{
+ TRACE();
+
+ if (mp_focus)
+ set_contained_view(toggle, mp_focus);
+}
+
+void
+Model::set_contained_view(Toggle toggle, View_ptr view)
+{
+ TRACE();
+
+ switch (toggle) {
+ case Toggle::On:
+ {
+ view->m_contained = true;
+
+ Workspace_ptr workspace = view->mp_workspace;
+
+ apply_layout(workspace);
+ return;
+ }
+ case Toggle::Off:
+ {
+ view->m_contained = false;
+
+ Workspace_ptr workspace = view->mp_workspace;
+
+ apply_layout(workspace);
+ return;
+ }
+ case Toggle::Reverse:
+ {
+ set_contained_view(
+ view->m_contained
+ ? Toggle::Off
+ : Toggle::On,
+ view
+ );
+
+ return;
+ }
+ default: return;
+ }
+}
+
+void
+Model::set_invincible_focus(Toggle toggle)
+{
+ TRACE();
+
+ if (mp_focus)
+ set_invincible_view(toggle, mp_focus);
+}
+
+void
+Model::set_invincible_view(Toggle toggle, View_ptr view)
+{
+ TRACE();
+
+ if (toggle == Toggle::Reverse)
+ set_invincible_view(
+ view->m_invincible
+ ? Toggle::Off
+ : Toggle::On,
+ view
+ );
+ else
+ view->m_invincible
+ = toggle == Toggle::On ? true : false;
+}
+
+void
+Model::set_iconifyable_focus(Toggle toggle)
+{
+ TRACE();
+
+ if (mp_focus)
+ set_iconifyable_view(toggle, mp_focus);
+}
+
+void
+Model::set_iconifyable_view(Toggle toggle, View_ptr view)
+{
+ TRACE();
+
+ if (toggle == Toggle::Reverse)
+ set_iconifyable_view(
+ view->m_iconifyable
+ ? Toggle::Off
+ : Toggle::On,
+ view
+ );
+ else
+ view->m_iconifyable
+ = toggle == Toggle::On ? true : false;
+}
+
+void
+Model::set_iconify_focus(Toggle toggle)
+{
+ TRACE();
+
+ if (mp_focus)
+ set_iconify_view(toggle, mp_focus);
+}
+
+void
+Model::set_iconify_view(Toggle toggle, View_ptr view)
+{
+ TRACE();
+
+ switch (toggle) {
+ case Toggle::On:
+ {
+ if (view->m_iconified || view->m_sticky)
+ return;
+
+ Workspace_ptr workspace = view->mp_workspace;
+ workspace->view_to_icon(view);
+
+ // TODO: set iconify state
+
+ unmap_view(view);
+
+ apply_layout(workspace);
+ sync_focus();
+
+ view->m_iconified = true;
+
+ return;
+ }
+ case Toggle::Off:
+ {
+ if (!view->m_iconified)
+ return;
+
+ Workspace_ptr workspace = view->mp_workspace;
+ workspace->icon_to_view(view);
+
+ // TODO: unset iconify state
+
+ view->m_iconified = false;
+
+ apply_layout(workspace);
+ sync_focus();
+
+ return;
+ }
+ case Toggle::Reverse:
+ {
+ set_iconify_view(
+ view->m_iconified
+ ? Toggle::Off
+ : Toggle::On,
+ view
+ );
+
+ return;
+ }
+ default: return;
+ }
+}
+
+void
+Model::center_focus()
+{
+ TRACE();
+
+ if (mp_focus)
+ center_view(mp_focus);
+}
+
+void
+Model::center_view(View_ptr view)
+{
+ TRACE();
+
+ if (!is_free(view))
+ return;
+
+ Region region = view->m_free_region;
+ const Region screen_region
+ = view->mp_context->output()->placeable_region();
+
+ region.pos.x = screen_region.pos.x
+ + (screen_region.dim.w - region.dim.w) / 2;
+
+ region.pos.y = screen_region.pos.y
+ + (screen_region.dim.h - region.dim.h) / 2;
+
+ Placement placement = Placement {
+ Placement::PlacementMethod::Free,
+ view,
+ view->m_free_decoration,
+ region
+ };
+
+ place_view(placement);
+}
+
+void
+Model::nudge_focus(Edge edge, Util::Change<std::size_t> change)
+{
+ TRACE();
+
+ if (mp_focus)
+ nudge_view(edge, change, mp_focus);
+}
+
+void
+Model::nudge_view(Edge edge, Util::Change<std::size_t> change, View_ptr view)
+{
+ TRACE();
+
+ if (!is_free(view))
+ return;
+
+ Region region = view->m_free_region;
+
+ switch (edge) {
+ case Edge::Left:
+ {
+ region.pos.x -= change;
+ break;
+ }
+ case Edge::Top:
+ {
+ region.pos.y -= change;
+ break;
+ }
+ case Edge::Right:
+ {
+ region.pos.x += change;
+ break;
+ }
+ case Edge::Bottom:
+ {
+ region.pos.y += change;
+ break;
+ }
+ default: return;
+ }
+
+ Placement placement = Placement {
+ Placement::PlacementMethod::Free,
+ view,
+ view->m_free_decoration,
+ region
+ };
+
+ place_view(placement);
+}
+
+void
+Model::stretch_focus(Edge edge, Util::Change<int> change)
+{
+ if (mp_focus)
+ stretch_view(edge, change, mp_focus);
+}
+
+void
+Model::stretch_view(Edge edge, Util::Change<int> change, View_ptr view)
+{
+ TRACE();
+
+ if (!is_free(view))
+ return;
+
+ Decoration decoration = view->m_free_decoration;
+ Extents extents = Extents { 0, 0, 0, 0 };
+
+ if (decoration.frame) {
+ extents.left += decoration.frame->extents.left;
+ extents.top += decoration.frame->extents.top;
+ extents.right += decoration.frame->extents.right;
+ extents.bottom += decoration.frame->extents.bottom;
+ }
+
+ Region region = view->m_free_region;
+ region.remove_extents(extents);
+
+ switch (edge) {
+ case Edge::Left:
+ {
+ if (!(change < 0 && -change >= region.dim.h)) {
+ if (region.dim.w + change <= View::MIN_VIEW_DIM.w) {
+ region.pos.x -= View::MIN_VIEW_DIM.w - region.dim.w;
+ region.dim.w = View::MIN_VIEW_DIM.w;
+ } else {
+ region.pos.x -= change;
+ region.dim.w += change;
+ }
+ }
+
+ break;
+ }
+ case Edge::Top:
+ {
+ if (!(change < 0 && -change >= region.dim.h)) {
+ if (region.dim.h + change <= View::MIN_VIEW_DIM.h) {
+ region.pos.y -= View::MIN_VIEW_DIM.h - region.dim.h;
+ region.dim.h = View::MIN_VIEW_DIM.h;
+ } else {
+ region.pos.y -= change;
+ region.dim.h += change;
+ }
+ }
+
+ break;
+ }
+ case Edge::Right:
+ {
+ if (!(change < 0 && -change >= region.dim.w)) {
+ if (region.dim.w + change <= View::MIN_VIEW_DIM.w)
+ region.dim.w = View::MIN_VIEW_DIM.w;
+ else
+ region.dim.w += change;
+ }
+
+ break;
+ }
+ case Edge::Bottom:
+ {
+ if (!(change < 0 && -change >= region.dim.h)) {
+ if (region.dim.h + change <= View::MIN_VIEW_DIM.h)
+ region.dim.h = View::MIN_VIEW_DIM.h;
+ else
+ region.dim.h += change;
+ }
+
+ break;
+ }
+ default: return;
+ }
+
+ region.apply_extents(extents);
+
+ Placement placement = Placement {
+ Placement::PlacementMethod::Free,
+ view,
+ view->m_free_decoration,
+ region
+ };
+
+ place_view(placement);
+}
+
+void
+Model::inflate_focus(Util::Change<int> change)
+{
+ TRACE();
+
+ if (mp_focus)
+ inflate_view(change, mp_focus);
+}
+
+void
+Model::inflate_view(Util::Change<int> change, View_ptr view)
+{
+ TRACE();
+
+ if (!is_free(view))
+ return;
+
+ Decoration decoration = view->m_free_decoration;
+ Extents extents = Extents { 0, 0, 0, 0 };
+
+ if (decoration.frame) {
+ extents.left += decoration.frame->extents.left;
+ extents.top += decoration.frame->extents.top;
+ extents.right += decoration.frame->extents.right;
+ extents.bottom += decoration.frame->extents.bottom;
+ }
+
+ Region region = view->m_free_region;
+ region.remove_extents(extents);
+
+ double ratio = static_cast<double>(region.dim.w)
+ / static_cast<double>(region.dim.w + region.dim.h);
+
+ double width_inc = ratio * change;
+ double height_inc = change - width_inc;
+
+ int dw = std::lround(width_inc);
+ int dh = std::lround(height_inc);
+
+ if ((dw < 0 && -dw >= region.dim.w)
+ || (dh < 0 && -dh >= region.dim.h)
+ || (region.dim.w + dw <= View::MIN_VIEW_DIM.w)
+ || (region.dim.h + dh <= View::MIN_VIEW_DIM.h))
+ {
+ return;
+ }
+
+ region.dim.w += dw;
+ region.dim.h += dh;
+
+ region.apply_extents(extents);
+
+ int dx = region.dim.w - view->m_free_region.dim.w;
+ int dy = region.dim.h - view->m_free_region.dim.h;
+
+ dx = std::lround(dx / static_cast<double>(2));
+ dy = std::lround(dy / static_cast<double>(2));
+
+ region.pos.x -= dx;
+ region.pos.y -= dy;
+
+ view->set_free_region(region);
+
+ Placement placement = Placement {
+ Placement::PlacementMethod::Free,
+ view,
+ view->m_free_decoration,
+ region
+ };
+
+ place_view(placement);
+}
+
+void
+Model::snap_focus(Edge edge)
+{
+ TRACE();
+
+ if (mp_focus)
+ snap_view(edge, mp_focus);
+}
+
+void
+Model::snap_view(Edge edge, View_ptr view)
+{
+ TRACE();
+
+ if (!is_free(view))
+ return;
+
+ Region region = view->m_free_region;
+ const Region screen_region
+ = view->mp_context->output()->placeable_region();
+
+ switch (edge) {
+ case Edge::Left:
+ {
+ region.pos.x = screen_region.pos.x;
+
+ break;
+ }
+ case Edge::Top:
+ {
+ region.pos.y = screen_region.pos.y;
+
+ break;
+ }
+ case Edge::Right:
+ {
+ region.pos.x = std::max(
+ 0,
+ (screen_region.dim.w + screen_region.pos.x) - region.dim.w
+ );
+
+ break;
+ }
+ case Edge::Bottom:
+ {
+ region.pos.y = std::max(
+ 0,
+ (screen_region.dim.h + screen_region.pos.y) - region.dim.h
+ );
+
+ break;
+ }
+ }
+
+ Placement placement = Placement {
+ Placement::PlacementMethod::Free,
+ view,
+ view->m_free_decoration,
+ region
+ };
+
+ place_view(placement);
+}
+
+void
+Model::pop_deiconify()
+{
+ TRACE();
+
+ std::optional<View_ptr> icon = mp_workspace->pop_icon();
+
+ if (icon)
+ set_iconify_view(Toggle::Off, *icon);
+}
+
+void
+Model::deiconify_all()
+{
+ TRACE();
+
+ for (std::size_t i = 0; i < mp_workspace->size(); ++i)
+ pop_deiconify();
+}
+
XDGView_ptr
Model::create_xdg_shell_view(
struct wlr_xdg_surface* wlr_xdg_surface,
diff --git a/src/kranewl/tree/client.cc b/src/kranewl/tree/client.cc
@@ -50,8 +50,9 @@ Client::Client(
m_disowned(false),
m_producing(true),
m_attaching(false),
- m_last_focused{std::chrono::steady_clock::now()},
- m_managed_since{std::chrono::steady_clock::now()},
+ m_last_focused(std::chrono::steady_clock::now()),
+ m_last_touched(std::chrono::steady_clock::now()),
+ m_managed_since(std::chrono::steady_clock::now()),
m_outside_state{OutsideState::Unfocused}
{}
diff --git a/src/kranewl/tree/output.cc b/src/kranewl/tree/output.cc
@@ -2,6 +2,7 @@
#include <kranewl/model.hh>
#include <kranewl/server.hh>
+#include <kranewl/workspace.hh>
#include <kranewl/tree/output.hh>
// https://github.com/swaywm/wlroots/issues/682
diff --git a/src/kranewl/tree/view.cc b/src/kranewl/tree/view.cc
@@ -45,9 +45,9 @@ View::View(
mp_workspace(nullptr),
mp_wlr_surface(wlr_surface),
m_alpha(1.f),
- m_tile_decoration{FREE_DECORATION},
- m_free_decoration{FREE_DECORATION},
- m_active_decoration{FREE_DECORATION},
+ m_tile_decoration(FREE_DECORATION),
+ m_free_decoration(FREE_DECORATION),
+ m_active_decoration(FREE_DECORATION),
m_minimum_dim({}),
m_preferred_dim({}),
m_free_region({}),
@@ -69,6 +69,7 @@ View::View(
m_iconified(false),
m_disowned(false),
m_last_focused(std::chrono::steady_clock::now()),
+ m_last_touched(std::chrono::steady_clock::now()),
m_managed_since(std::chrono::steady_clock::now()),
ml_foreign_activate_request({ .notify = handle_foreign_activate_request }),
ml_foreign_fullscreen_request({ .notify = handle_foreign_fullscreen_request }),
@@ -200,10 +201,15 @@ View::unmap()
{
TRACE();
- wlr_scene_node_destroy(mp_scene_surface);
wlr_scene_node_destroy(mp_scene);
}
+void
+View::touch()
+{
+ m_last_touched = std::chrono::steady_clock::now();
+}
+
static uint32_t
extents_to_wlr_edges(Extents const& extents)
{
diff --git a/src/kranewl/tree/xdg_view.cc b/src/kranewl/tree/xdg_view.cc
@@ -66,13 +66,13 @@ XDGView::focus(bool raise)
{
TRACE();
- struct wlr_surface* focused_surface
+ struct wlr_surface* prev_focus
= mp_seat->mp_wlr_seat->keyboard_state.focused_surface;
if (raise)
wlr_scene_node_raise_to_top(mp_scene);
- if (focused_surface == mp_wlr_surface)
+ if (prev_focus == mp_wlr_surface)
return;
mp_model->focus_view(this);
@@ -83,10 +83,10 @@ XDGView::focus(bool raise)
m_active_decoration.colorscheme.focused.values
);
- if (focused_surface && focused_surface != mp_wlr_surface) {
- if (wlr_surface_is_layer_surface(focused_surface)) {
+ if (prev_focus && prev_focus != mp_wlr_surface) {
+ if (wlr_surface_is_layer_surface(prev_focus)) {
struct wlr_layer_surface_v1* wlr_layer_surface
- = wlr_layer_surface_v1_from_wlr_surface(focused_surface);
+ = wlr_layer_surface_v1_from_wlr_surface(prev_focus);
if (wlr_layer_surface->mapped && (
wlr_layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP ||
@@ -95,19 +95,20 @@ XDGView::focus(bool raise)
return;
}
} else {
+ View_ptr prev_view;
struct wlr_scene_node* node
- = reinterpret_cast<struct wlr_scene_node*>(focused_surface->data);
+ = reinterpret_cast<struct wlr_scene_node*>(prev_focus->data);
- if (focused_surface->role_data && node->data)
+ if (prev_focus->role_data && (prev_view = reinterpret_cast<View_ptr>(node->data)))
for (std::size_t i = 0; i < 4; ++i)
wlr_scene_rect_set_color(
- m_protrusions[i],
+ prev_view->m_protrusions[i],
m_active_decoration.colorscheme.unfocused.values
);
struct wlr_xdg_surface* wlr_xdg_surface;
- if (wlr_surface_is_xdg_surface(focused_surface)
- && (wlr_xdg_surface = wlr_xdg_surface_from_wlr_surface(focused_surface))
+ if (wlr_surface_is_xdg_surface(prev_focus)
+ && (wlr_xdg_surface = wlr_xdg_surface_from_wlr_surface(prev_focus))
&& wlr_xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL)
{
wlr_xdg_toplevel_set_activated(wlr_xdg_surface, false);
@@ -205,6 +206,10 @@ XDGView::handle_commit(struct wl_listener* listener, void* data)
{
TRACE();
+ XDGView_ptr view = wl_container_of(listener, view, ml_commit);
+
+ if (view->m_resize && view->m_resize <= view->mp_wlr_xdg_surface->current.configure_serial)
+ view->m_resize = 0;
}
void
@@ -233,6 +238,9 @@ XDGView::handle_set_title(struct wl_listener* listener, void* data)
{
TRACE();
+ XDGView_ptr view = wl_container_of(listener, view, ml_set_title);
+ view->m_title = view->mp_wlr_xdg_toplevel->title;
+ view->m_title_formatted = view->m_title;
}
void
@@ -240,6 +248,8 @@ XDGView::handle_set_app_id(struct wl_listener* listener, void* data)
{
TRACE();
+ XDGView_ptr view = wl_container_of(listener, view, ml_set_app_id);
+ view->m_app_id = view->mp_wlr_xdg_toplevel->app_id;
}
void
@@ -343,8 +353,10 @@ XDGView::handle_destroy(struct wl_listener* listener, void* data)
XDGView_ptr view = wl_container_of(listener, view, ml_destroy);
+ view->mp_wlr_xdg_toplevel = nullptr;
view->mp_model->unregister_view(view);
+ wl_list_remove(&view->m_events.unmap.listener_list);
wl_list_remove(&view->ml_commit.link);
wl_list_remove(&view->ml_new_popup.link);
wl_list_remove(&view->ml_request_fullscreen.link);
@@ -352,4 +364,7 @@ XDGView::handle_destroy(struct wl_listener* listener, void* data)
wl_list_remove(&view->ml_request_resize.link);
wl_list_remove(&view->ml_set_title.link);
wl_list_remove(&view->ml_set_app_id.link);
+ wl_list_remove(&view->ml_map.link);
+ wl_list_remove(&view->ml_unmap.link);
+ wl_list_remove(&view->ml_destroy.link);
}