diff --git a/data/css/default.css b/data/css/default.css index eed40763..f8c504d9 100644 --- a/data/css/default.css +++ b/data/css/default.css @@ -72,6 +72,41 @@ border-radius: 3px; color: #41A6B5; } + +.wf-panel .workspace-switcher { + background-color: #222222FF; + padding-top: 0px; + padding-bottom: 1px; + padding-right: 1px; + padding-left: 0px; +} + +.wf-panel .workspace-switcher .workspace.active { + background-color: #005577FF; + margin-top: 1px; + margin-left: 1px; +} + +.wf-panel .workspace-switcher .workspace.inactive { + background-color: #0077AAFF; + margin-top: 1px; + margin-left: 1px; +} + +.wf-panel .workspace-switcher .workspace .view { + background-color: #0000AA77; + border: 1px solid #222 +} + +.wf-panel .workspace-switcher .workspace .view.active { + background-color: #FF00AA77; + border: 1px solid #222 +} + +.wf-panel .workspace-switcher .workspace .view.inactive { + background-color: #00FFAA77; + border: 1px solid #222 +} .excellent { color: #00ff00; } diff --git a/metadata/panel.xml b/metadata/panel.xml index b59900e7..0db15b88 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -467,5 +467,28 @@ Set to -1 to only run it by clicking the button. 1 + + <_short>Workspace Switcher + + + + diff --git a/src/panel/meson.build b/src/panel/meson.build index bff4e6b3..4097c1d3 100644 --- a/src/panel/meson.build +++ b/src/panel/meson.build @@ -20,6 +20,7 @@ widget_sources = [ 'widgets/tray/item.cpp', 'widgets/tray/host.cpp', 'widgets/tray/dbusmenu.cpp', + 'widgets/workspace-switcher.cpp', ] deps = [ diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index e31615c6..5d38c21d 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -23,6 +23,7 @@ #include "widgets/network.hpp" #include "widgets/spacing.hpp" #include "widgets/separator.hpp" +#include "widgets/workspace-switcher.hpp" #ifdef HAVE_PULSE #include "widgets/volume.hpp" #endif @@ -211,6 +212,20 @@ class WayfirePanel::impl } } + if (name == "workspace-switcher") + { + if (WayfireIPC::get_instance()->connected) + { + return Widget(new WayfireWorkspaceSwitcher(output)); + } else + { + std::cerr << + "Wayfire IPC not connected, which is required to load workspace-switcher widget." << + std::endl; + return nullptr; + } + } + if (auto pixel = widget_with_value(name, "spacing")) { return Widget(new WayfireSpacing(*pixel)); diff --git a/src/panel/widgets/workspace-switcher.cpp b/src/panel/widgets/workspace-switcher.cpp new file mode 100644 index 00000000..2f130b70 --- /dev/null +++ b/src/panel/widgets/workspace-switcher.cpp @@ -0,0 +1,889 @@ +#include +#include +#include +#include "workspace-switcher.hpp" + +void WayfireWorkspaceSwitcher::init(Gtk::Box *container) +{ + box.add_css_class("workspace-switcher"); + box.add_css_class("flat"); + + ipc_client->subscribe(this, {"view-mapped"}); + ipc_client->subscribe(this, {"view-focused"}); + ipc_client->subscribe(this, {"view-unmapped"}); + ipc_client->subscribe(this, {"view-set-output"}); + ipc_client->subscribe(this, {"view-geometry-changed"}); + ipc_client->subscribe(this, {"output-layout-changed"}); + ipc_client->subscribe(this, {"wset-workspace-changed"}); + workspace_switcher_target_height.set_callback([=] () + { + get_wsets(); + }); + auto mode_cb = ([=] () + { + clear_switcher_box(); + if (std::string(workspace_switcher_mode) == "classic") + { + switcher_box.append(box); + } else // "popover" + { + switcher_box.append(grid); + } + + get_wsets(); + }); + workspace_switcher_mode.set_callback(mode_cb); + workspace_switcher_render_views.set_callback([=] () + { + get_wsets(); + }); + + switcher_box.add_css_class("workspace-switcher"); + auto click_gesture = Gtk::GestureClick::create(); + click_gesture->set_button(0); + click_gesture->signal_released().connect(sigc::mem_fun(*this, + &WayfireWorkspaceSwitcher::on_grid_clicked)); + grid.add_controller(click_gesture); + + container->append(switcher_box); + mode_cb(); +} + +void WayfireWorkspaceSwitcher::get_wsets() +{ + ipc_client->send("{\"method\":\"window-rules/list-wsets\"}", [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error getting wsets list for workspace-switcher widget!" << std::endl; + return; + } + + if (std::string(workspace_switcher_mode) == "classic") + { + process_workspaces(data); + } else // "popover" + { + popover_process_workspaces(data); + } + }); +} + +void WayfireWorkspaceSwitcher::clear_switcher_box() +{ + for (auto child : switcher_box.get_children()) + { + switcher_box.remove(*child); + } +} + +void WayfireWorkspaceSwitcher::clear_box() +{ + for (auto child : box.get_children()) + { + box.remove(*child); + } + + for (auto child : grid.get_children()) + { + grid.remove(*child); + } + + for (auto child : popover_grid.get_children()) + { + popover_grid.remove(*child); + } + + windows.clear(); +} + +std::pair WayfireWorkspaceSwitcher::get_workspace(WayfireWorkspaceBox *ws, + WayfireWorkspaceWindow *w) +{ + std::pair workspace; + double scaled_output_width = ws->get_scaled_width(); + double scaled_output_height = workspace_switcher_target_height; + workspace.first = std::floor((w->x + (w->w / 2)) / scaled_output_width) + this->current_ws_x; + workspace.second = std::floor((w->y + (w->h / 2)) / scaled_output_height) + this->current_ws_y; + return workspace; +} + +std::pair WayfireWorkspaceSwitcher::popover_get_workspace(WayfireWorkspaceWindow *w) +{ + std::pair workspace; + double scaled_output_width = this->get_scaled_width(); + double scaled_output_height = workspace_switcher_target_height; + workspace.first = std::floor((w->x + (w->w / 2)) / scaled_output_width) + this->current_ws_x; + workspace.second = std::floor((w->y + (w->h / 2)) / scaled_output_height) + this->current_ws_y; + return workspace; +} + +bool WayfireWorkspaceSwitcher::on_get_child_position(Gtk::Widget *widget, Gdk::Rectangle& allocation) +{ + if (auto w = static_cast(widget)) + { + allocation.set_x(w->x + (this->current_ws_x - w->x_index) * w->ws->get_width()); + allocation.set_y(w->y); + allocation.set_width(w->w); + allocation.set_height(w->h); + return true; + } + + return false; +} + +bool WayfireWorkspaceSwitcher::on_popover_get_child_position(Gtk::Widget *widget, Gdk::Rectangle& allocation) +{ + if (auto w = static_cast(widget)) + { + allocation.set_x(w->x + this->current_ws_x * this->get_scaled_width()); + allocation.set_y(w->y + this->current_ws_y * this->workspace_switcher_target_height); + allocation.set_width(w->w); + allocation.set_height(w->h); + return true; + } + + return false; +} + +void WayfireWorkspaceBox::on_popover_grid_clicked(int count, double x, double y) +{ + wf::json_t workspace_switch_request; + workspace_switch_request["method"] = "vswitch/set-workspace"; + wf::json_t workspace; + workspace["x"] = this->switcher->current_ws_x = this->x_index; + workspace["y"] = this->switcher->current_ws_y = this->y_index; + workspace["output-id"] = this->output_id; + workspace_switch_request["data"] = workspace; + this->switcher->ipc_client->send(workspace_switch_request.serialize(), [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error switching workspaces. Is vswitch plugin enabled?" << std::endl; + } + }); + for (auto widget : this->switcher->grid.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + if ((ws->x_index == this->x_index) && (ws->y_index == this->y_index)) + { + ws->remove_css_class("inactive"); + ws->add_css_class("active"); + } else + { + ws->add_css_class("inactive"); + ws->remove_css_class("active"); + } + } +} + +void WayfireWorkspaceSwitcher::on_grid_clicked(int count, double x, double y) +{ + this->popover->popup(); +} + +void WayfireWorkspaceBox::on_workspace_clicked(int count, double x, double y) +{ + wf::json_t workspace_switch_request; + workspace_switch_request["method"] = "vswitch/set-workspace"; + wf::json_t workspace; + workspace["x"] = this->switcher->current_ws_x = this->x_index; + workspace["y"] = this->switcher->current_ws_y = this->y_index; + workspace["output-id"] = this->output_id; + workspace_switch_request["data"] = workspace; + this->switcher->ipc_client->send(workspace_switch_request.serialize(), [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error switching workspaces. Is vswitch plugin enabled?" << std::endl; + } + }); + for (auto widget : this->switcher->box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + if ((ws->x_index == this->x_index) && (ws->y_index == this->y_index)) + { + ws->remove_css_class("inactive"); + ws->add_css_class("active"); + } else + { + ws->add_css_class("inactive"); + ws->remove_css_class("active"); + } + } +} + +double WayfireWorkspaceSwitcher::get_scaled_width() +{ + return this->workspace_switcher_target_height * + (this->output_width / float(this->output_height)); +} + +int WayfireWorkspaceBox::get_scaled_width() +{ + return this->switcher->workspace_switcher_target_height * + (this->output_width / float(this->output_height)); +} + +bool WayfireWorkspaceBox::on_workspace_scrolled(double x, double y) +{ + for (auto widget : this->switcher->box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + if (y > 0) + { + if (ws->y_index < ws->switcher->grid_height - 1) + { + ws->y_index++; + } + } else + { + if (ws->y_index > 0) + { + ws->y_index--; + } + } + } + + wf::json_t workspace_switch_request; + workspace_switch_request["method"] = "vswitch/set-workspace"; + wf::json_t workspace; + workspace["x"] = this->switcher->current_ws_x = this->x_index; + workspace["y"] = this->switcher->current_ws_y = this->y_index; + workspace["output-id"] = this->output_id; + workspace_switch_request["data"] = workspace; + this->switcher->ipc_client->send(workspace_switch_request.serialize(), [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error switching workspaces. Is vswitch plugin enabled?" << std::endl; + } + }); + for (auto widget : this->switcher->box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + if ((ws->x_index == this->x_index) && (ws->y_index == this->y_index)) + { + ws->remove_css_class("inactive"); + ws->add_css_class("active"); + } else + { + ws->add_css_class("inactive"); + ws->remove_css_class("active"); + } + } + + return false; +} + +void WayfireWorkspaceSwitcher::render_workspace(wf::json_t workspace, int j, int output_id, int output_width, + int output_height) +{ + auto ws = Gtk::make_managed(this); + ws->x_index = j; + ws->y_index = workspace["workspace"]["y"].as_int(); + ws->output_id = output_id; + ws->output_width = output_width; + ws->output_height = output_height; + ws->add_css_class("workspace"); + if (workspace["workspace"]["x"].as_int() == j) + { + ws->add_css_class("active"); + this->current_ws_x = j; + this->current_ws_y = ws->y_index; + } else + { + ws->add_css_class("inactive"); + } + + ws->signal_get_child_position().connect(sigc::mem_fun(*this, + &WayfireWorkspaceSwitcher::on_get_child_position), false); + auto click_gesture = Gtk::GestureClick::create(); + click_gesture->set_button(0); + click_gesture->signal_released().connect(sigc::mem_fun(*ws, &WayfireWorkspaceBox::on_workspace_clicked)); + auto scroll_controller = Gtk::EventControllerScroll::create(); + scroll_controller->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL); + scroll_controller->signal_scroll().connect(sigc::mem_fun(*ws, + &WayfireWorkspaceBox::on_workspace_scrolled), + false); + ws->set_hexpand(false); + ws->set_vexpand(false); + ws->set_size_request(ws->get_scaled_width(), workspace_switcher_target_height); + ws->add_controller(click_gesture); + ws->add_controller(scroll_controller); + box.append(*ws); + if (workspace_switcher_render_views && (j == this->grid_width - 1)) + { + ipc_client->send("{\"method\":\"window-rules/list-views\"}", [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error getting views list for workspace-switcher widget!" << std::endl; + return; + } + + render_views(data); + }); + } +} + +void WayfireWorkspaceSwitcher::process_workspaces(wf::json_t workspace_data) +{ + size_t i = 0; + + this->grid_width = workspace_data[i]["workspace"]["grid_width"].as_int(); + this->grid_height = workspace_data[i]["workspace"]["grid_height"].as_int(); + + for (i = 0; i < workspace_data.size(); i++) + { + wf::json_t output_info_request; + output_info_request["method"] = "window-rules/output-info"; + wf::json_t output_id; + output_id["id"] = workspace_data[i]["output-id"].as_int(); + output_info_request["data"] = output_id; + ipc_client->send(output_info_request.serialize(), [=] (wf::json_t output_data) + { + if (output_data.serialize().find("error") != std::string::npos) + { + std::cerr << output_data.serialize() << std::endl; + std::cerr << "Error getting output information!" << std::endl; + return; + } + + if (this->output_name == output_data["name"].as_string()) + { + auto output_id = output_data["id"].as_int(); + auto output_width = output_data["geometry"]["width"].as_int(); + auto output_height = output_data["geometry"]["height"].as_int(); + for (auto w : windows) + { + if (w->active) + { + this->active_view_id = w->id; + break; + } + } + + clear_box(); + for (int j = 0; j < this->grid_width; j++) + { + render_workspace(workspace_data[i], j, output_id, output_width, output_height); + } + } + }); + } +} + +void WayfireWorkspaceSwitcher::popover_process_workspaces(wf::json_t workspace_data) +{ + size_t i = 0; + + this->grid_width = workspace_data[i]["workspace"]["grid_width"].as_int(); + this->grid_height = workspace_data[i]["workspace"]["grid_height"].as_int(); + + for (i = 0; i < workspace_data.size(); i++) + { + wf::json_t output_info_request; + output_info_request["method"] = "window-rules/output-info"; + wf::json_t output_id; + output_id["id"] = workspace_data[i]["output-id"].as_int(); + output_info_request["data"] = output_id; + ipc_client->send(output_info_request.serialize(), [=] (wf::json_t output_data) + { + if (output_data.serialize().find("error") != std::string::npos) + { + std::cerr << output_data.serialize() << std::endl; + std::cerr << "Error getting output information!" << std::endl; + return; + } + + if (this->output_name == output_data["name"].as_string()) + { + this->output_width = output_data["geometry"]["width"].as_int(); + this->output_height = output_data["geometry"]["height"].as_int(); + for (auto w : windows) + { + if (w->active) + { + this->active_view_id = w->id; + break; + } + } + + clear_box(); + popover = Gtk::make_managed(); + popover->set_parent(grid); + popover->set_child(overlay); + overlay.set_child(popover_grid); + overlay.add_css_class("workspace"); + overlay.signal_get_child_position().connect(sigc::mem_fun(*this, + &WayfireWorkspaceSwitcher::on_popover_get_child_position), false); + for (int j = 0; j < this->grid_height; j++) + { + for (int k = 0; k < this->grid_width; k++) + { + auto ws = Gtk::make_managed(this); + ws->output_id = output_data["id"].as_int(); + ws->set_can_target(false); + auto ws_width = this->get_scaled_width() / this->grid_width; + auto ws_height = this->workspace_switcher_target_height / this->grid_height; + ws->set_size_request(ws_width, ws_height); + ws->add_css_class("workspace"); + if ((workspace_data[i]["workspace"]["x"].as_int() == k) && + (workspace_data[i]["workspace"]["y"].as_int() == j)) + { + ws->add_css_class("active"); + this->current_ws_x = k; + this->current_ws_y = j; + } else + { + ws->add_css_class("inactive"); + } + + ws->x_index = k; + ws->y_index = j; + grid.attach(*ws, ws->x_index, ws->y_index, 1, 1); + + ws = Gtk::make_managed(this); + ws->output_id = output_data["id"].as_int(); + ws->set_size_request( + this->get_scaled_width(), this->workspace_switcher_target_height); + ws->add_css_class("workspace"); + if ((workspace_data[i]["workspace"]["x"].as_int() == k) && + (workspace_data[i]["workspace"]["y"].as_int() == j)) + { + ws->add_css_class("active"); + this->current_ws_x = k; + this->current_ws_y = j; + } else + { + ws->add_css_class("inactive"); + } + + ws->x_index = k; + ws->y_index = j; + auto popover_click_gesture = Gtk::GestureClick::create(); + popover_click_gesture->set_button(0); + popover_click_gesture->signal_released().connect(sigc::mem_fun(*ws, + &WayfireWorkspaceBox::on_popover_grid_clicked)); + ws->add_controller(popover_click_gesture); + popover_grid.attach(*ws, ws->x_index, ws->y_index, 1, 1); + if (workspace_switcher_render_views && (j == this->grid_height - 1) && + (k == this->grid_width - 1)) + { + ipc_client->send("{\"method\":\"window-rules/list-views\"}", [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error getting views list for workspace-switcher widget!" << + std::endl; + return; + } + + popover_render_views(data); + }); + } + } + } + } + }); + } +} + +void WayfireWorkspaceSwitcher::add_view(wf::json_t view_data) +{ + if (view_data["type"].as_string() != "toplevel") + { + return; + } + + if (view_data["output-name"].as_string() != this->output_name) + { + return; + } + + auto v = Gtk::make_managed(); + v->add_css_class("view"); + v->add_css_class(view_data["app-id"].as_string()); + v->id = view_data["id"].as_int(); + if (view_data["activated"].as_bool() || (v->id == this->active_view_id)) + { + v->add_css_class("active"); + v->active = true; + this->active_view_id = v->id; + } else + { + v->add_css_class("inactive"); + v->active = false; + } + + v->output_id = view_data["output-id"].as_int(); + auto x = view_data["geometry"]["x"].as_int(); + auto y = view_data["geometry"]["y"].as_int(); + auto w = view_data["geometry"]["width"].as_int(); + auto h = view_data["geometry"]["height"].as_int(); + for (auto widget : box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + for (auto child : widget->get_children()) + { + WayfireWorkspaceWindow *window = (WayfireWorkspaceWindow*)child; + if (window->id == v->id) + { + return; + } + } + + double width = ws->get_scaled_width(); + double height = workspace_switcher_target_height; + + v->x = x * (width / float(ws->output_width)); + v->y = y * (height / float(ws->output_height)); + v->w = (w / float(ws->output_width)) * width; + v->h = (h / float(ws->output_height)) * height; + std::pair workspace; + workspace = get_workspace(ws, v); + v->x_index = workspace.first; + v->y_index = workspace.second; + if ((v->x_index == ws->x_index) && (v->y_index == ws->y_index)) + { + v->ws = ws; + v->set_can_target(false); + // add to workspace box + ws->add_overlay(*v); + windows.push_back(v); + + return; + } + } +} + +void WayfireWorkspaceSwitcher::popover_add_view(wf::json_t view_data) +{ + if (view_data["type"].as_string() != "toplevel") + { + return; + } + + if (view_data["output-name"].as_string() != this->output_name) + { + return; + } + + auto v = Gtk::make_managed(); + v->add_css_class("view"); + v->add_css_class(view_data["app-id"].as_string()); + v->id = view_data["id"].as_int(); + if (view_data["activated"].as_bool() || (v->id == this->active_view_id)) + { + v->add_css_class("active"); + v->active = true; + this->active_view_id = v->id; + } else + { + v->add_css_class("inactive"); + v->active = false; + } + + v->output_id = view_data["output-id"].as_int(); + auto x = view_data["geometry"]["x"].as_int(); + auto y = view_data["geometry"]["y"].as_int(); + auto w = view_data["geometry"]["width"].as_int(); + auto h = view_data["geometry"]["height"].as_int(); + for (auto widget : overlay.get_children()) + { + WayfireWorkspaceWindow *window = (WayfireWorkspaceWindow*)widget; + if (window->id == v->id) + { + return; + } + + double width = this->get_scaled_width(); + double height = workspace_switcher_target_height; + + v->x = x * (width / float(this->output_width)); + v->y = y * (height / float(this->output_height)); + v->w = (w / float(this->output_width)) * width; + v->h = (h / float(this->output_height)) * height; + std::pair workspace; + workspace = popover_get_workspace(v); + v->x_index = workspace.first; + v->y_index = workspace.second; + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)&overlay; + v->ws = ws; + v->set_can_target(false); + // add to workspace box + overlay.add_overlay(*v); + windows.push_back(v); + + return; + } +} + +void WayfireWorkspaceSwitcher::remove_view(wf::json_t view_data) +{ + for (auto w : this->windows) + { + if (w->id == view_data["id"].as_int()) + { + for (auto widget : box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + if (w->get_parent() == ws) + { + ws->remove_overlay(*w); + auto elem = std::remove(windows.begin(), windows.end(), w); + windows.erase(elem, windows.end()); + return; + } + } + } + } +} + +void WayfireWorkspaceSwitcher::popover_remove_view(wf::json_t view_data) +{ + for (auto w : this->windows) + { + if (w->id == view_data["id"].as_int()) + { + for (auto widget : overlay.get_children()) + { + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; + overlay.remove_overlay(*w); + auto elem = std::remove(windows.begin(), windows.end(), w); + windows.erase(elem, windows.end()); + return; + } + } + } +} + +void WayfireWorkspaceSwitcher::render_views(wf::json_t views_data) +{ + for (size_t i = 0; i < views_data.size(); i++) + { + add_view(views_data[i]); + } + + for (auto w : windows) + { + if (w->active) + { + w->reference(); + w->ws->remove_overlay(*w); + w->ws->add_overlay(*w); + w->unreference(); + w->add_css_class("active"); + } + } +} + +void WayfireWorkspaceSwitcher::popover_render_views(wf::json_t views_data) +{ + for (size_t i = 0; i < views_data.size(); i++) + { + popover_add_view(views_data[i]); + } + + for (auto w : windows) + { + if (w->gobj() == GTK_WIDGET(popover_grid.gobj())) + { + continue; + } + + if (w->active) + { + w->reference(); + w->ws->remove_overlay(*w); + w->ws->add_overlay(*w); + w->unreference(); + w->add_css_class("active"); + } + } +} + +void WayfireWorkspaceSwitcher::on_event(wf::json_t data) +{ + if (std::string(workspace_switcher_mode) == "classic") + { + switcher_on_event(data); + } else // "popover" + { + popover_on_event(data); + } +} + +void WayfireWorkspaceSwitcher::switcher_on_event(wf::json_t data) +{ + if (data["event"].as_string() == "view-geometry-changed") + { + for (auto child : box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)child; + for (auto widget : child->get_children()) + { + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; + if (w->id == data["view"]["id"].as_int()) + { + ws->remove_overlay(*w); + auto elem = std::remove(windows.begin(), windows.end(), w); + windows.erase(elem, windows.end()); + } + } + } + + add_view(data["view"]); + } else if (data["event"].as_string() == "view-mapped") + { + add_view(data["view"]); + } else if ((data["event"].as_string() == "view-focused") && data["view"].is_object()) + { + if (data["view"]["type"].as_string() != "toplevel") + { + return; + } + + for (auto child : box.get_children()) + { + for (auto widget : child->get_children()) + { + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; + if (w->id == data["view"]["id"].as_int()) + { + w->remove_css_class("inactive"); + w->add_css_class("active"); + w->active = true; + this->active_view_id = w->id; + } else + { + w->remove_css_class("active"); + w->add_css_class("inactive"); + w->active = false; + } + } + } + + for (auto w : windows) + { + if (w->active) + { + w->reference(); + w->ws->remove_overlay(*w); + w->ws->add_overlay(*w); + w->unreference(); + w->add_css_class("active"); + this->active_view_id = w->id; + } + } + } else if (data["event"].as_string() == "view-unmapped") + { + remove_view(data["view"]); + } else if (data["event"].as_string() == "view-set-output") + { + add_view(data["view"]); + } else if ((data["event"].as_string() == "output-layout-changed") || + (data["event"].as_string() == "wset-workspace-changed")) + { + get_wsets(); + } +} + +void WayfireWorkspaceSwitcher::popover_on_event(wf::json_t data) +{ + if (data["event"].as_string() == "view-geometry-changed") + { + for (auto widget : overlay.get_children()) + { + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; + if (w->id == data["view"]["id"].as_int()) + { + overlay.remove_overlay(*w); + auto elem = std::remove(windows.begin(), windows.end(), w); + windows.erase(elem, windows.end()); + } + } + + popover_add_view(data["view"]); + } else if (data["event"].as_string() == "view-mapped") + { + popover_add_view(data["view"]); + } else if ((data["event"].as_string() == "view-focused") && data["view"].is_object()) + { + if (data["view"]["type"].as_string() != "toplevel") + { + return; + } + + for (auto widget : overlay.get_children()) + { + if (widget->gobj() == GTK_WIDGET(popover_grid.gobj())) + { + continue; + } + + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; + if (w->id == data["view"]["id"].as_int()) + { + w->remove_css_class("inactive"); + w->add_css_class("active"); + w->active = true; + this->active_view_id = w->id; + } else + { + w->remove_css_class("active"); + w->add_css_class("inactive"); + w->active = false; + } + } + + for (auto w : windows) + { + if (w->gobj() == GTK_WIDGET(popover_grid.gobj())) + { + continue; + } + + if (w->active) + { + w->reference(); + w->ws->remove_overlay(*w); + w->ws->add_overlay(*w); + w->unreference(); + w->add_css_class("active"); + this->active_view_id = w->id; + } + } + } else if (data["event"].as_string() == "view-unmapped") + { + popover_remove_view(data["view"]); + } else if (data["event"].as_string() == "view-set-output") + { + popover_add_view(data["view"]); + } else if ((data["event"].as_string() == "output-layout-changed") || + (data["event"].as_string() == "wset-workspace-changed")) + { + get_wsets(); + } +} + +WayfireWorkspaceSwitcher::WayfireWorkspaceSwitcher(WayfireOutput *output) +{ + this->output_name = output->monitor->get_connector(); + ipc_client = WayfireIPC::get_instance()->create_client(); + switcher_box.set_vexpand(false); + switcher_box.set_valign(Gtk::Align::CENTER); +} + +WayfireWorkspaceSwitcher::~WayfireWorkspaceSwitcher() +{ + ipc_client->unsubscribe(this); + clear_box(); +} diff --git a/src/panel/widgets/workspace-switcher.hpp b/src/panel/widgets/workspace-switcher.hpp new file mode 100644 index 00000000..38cc9498 --- /dev/null +++ b/src/panel/widgets/workspace-switcher.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include + +#include "../widget.hpp" +#include "wf-ipc.hpp" + +class WayfireWorkspaceBox; +class WayfireWorkspaceWindow : public Gtk::Widget +{ + public: + int x = 0, y = 0, w = 10, h = 10; + int x_index, y_index; + int id, output_id; + bool active; + WayfireWorkspaceBox *ws; + WayfireWorkspaceWindow() + {} + ~WayfireWorkspaceWindow() override + {} +}; + +class WayfireWorkspaceSwitcher : public WayfireWidget, public IIPCSubscriber +{ + std::string output_name; + void on_event(wf::json_t data) override; + void switcher_on_event(wf::json_t data); + void popover_on_event(wf::json_t data); + void render_workspace(wf::json_t workspace_data, int j, int output_id, int output_width, + int output_height); + void process_workspaces(wf::json_t workspace_data); + void popover_process_workspaces(wf::json_t workspace_data); + void render_views(wf::json_t views_data); + void popover_render_views(wf::json_t views_data); + void add_view(wf::json_t view_data); + void popover_add_view(wf::json_t view_data); + void remove_view(wf::json_t view_data); + void popover_remove_view(wf::json_t view_data); + void clear_switcher_box(); + void clear_box(); + void get_wsets(); + bool on_get_child_position(Gtk::Widget *widget, Gdk::Rectangle& allocation); + bool on_popover_get_child_position(Gtk::Widget *widget, Gdk::Rectangle& allocation); + + public: + Gtk::Box box; + Gtk::Grid grid; + Gtk::Box switcher_box; + Gtk::Popover *popover; + Gtk::Grid popover_grid; + Gtk::Overlay overlay; + double get_scaled_width(); + int output_width, output_height; + void init(Gtk::Box *container) override; + WayfireWorkspaceSwitcher(WayfireOutput *output); + ~WayfireWorkspaceSwitcher(); + int grid_width, grid_height; + int active_view_id; + std::shared_ptr ipc_client; + std::pair get_workspace(WayfireWorkspaceBox *ws, WayfireWorkspaceWindow *w); + std::pair popover_get_workspace(WayfireWorkspaceWindow *w); + int current_ws_x, current_ws_y; + std::vector windows; + void on_grid_clicked(int count, double x, double y); + WfOption workspace_switcher_mode{"panel/workspace_switcher_mode"}; + WfOption workspace_switcher_target_height{"panel/workspace_switcher_target_height"}; + WfOption workspace_switcher_render_views{"panel/workspace_switcher_render_views"}; +}; + +class WayfireWorkspaceBox : public Gtk::Overlay +{ + WayfireWorkspaceSwitcher *switcher; + + public: + int x_index, y_index; + int output_id, output_width, output_height; + int get_scaled_width(); + WayfireWorkspaceBox(WayfireWorkspaceSwitcher *switcher) + { + this->switcher = switcher; + } + + ~WayfireWorkspaceBox() override + {} + void on_popover_grid_clicked(int count, double x, double y); + void on_workspace_clicked(int count, double x, double y); + bool on_workspace_scrolled(double x, double y); +};