window layouter: internal restructuring

This patch splits the implementation of the window layouter into several
headers to ease the upcoming addition of new functionality.
This commit is contained in:
Norman Feske 2015-12-04 16:17:22 +01:00
parent ff8d790f93
commit 37044eaad8
5 changed files with 851 additions and 589 deletions

View File

@ -24,30 +24,22 @@
#include <rom_session/connection.h>
#include <decorator/xml_utils.h>
/* local includes */
#include "window.h"
#include "user_state.h"
#include "operations.h"
namespace Floating_window_layouter {
using namespace Genode;
struct Main;
class Window;
typedef Decorator::Point Point;
typedef Decorator::Area Area;
typedef Decorator::Rect Rect;
using Decorator::attribute;
using Decorator::string_attribute;
using Decorator::area_attribute;
using Decorator::point_attribute;
static Xml_node xml_lookup_window_by_id(Xml_node node, unsigned const id)
static Xml_node xml_lookup_window_by_id(Xml_node node, Window_id const id)
{
char const *tag = "window";
char const *id_attr = "id";
for (node = node.sub_node(tag); ; node = node.next(tag))
if (attribute(node, id_attr, 0UL) == id)
if (attribute(node, id_attr, 0UL) == id.value)
return node;
throw Xml_node::Nonexistent_sub_node();
@ -58,323 +50,21 @@ namespace Floating_window_layouter {
* Return true if compound XML node contains a sub node with ID
*/
static bool xml_contains_window_node_with_id(Xml_node node,
unsigned const id)
Window_id const id)
{
try { xml_lookup_window_by_id(node, id); return true; }
try { xml_lookup_window_by_id(node, id.value); return true; }
catch (Xml_node::Nonexistent_sub_node) { return false; }
}
}
class Floating_window_layouter::Window : public List<Window>::Element
{
public:
typedef String<256> Title;
typedef String<256> Label;
struct Element
{
enum Type { UNDEFINED, TITLE, LEFT, RIGHT, TOP, BOTTOM,
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,
CLOSER, MAXIMIZER, MINIMIZER };
Type type;
char const *name() const
{
switch (type) {
case UNDEFINED: return "";
case TITLE: return "title";
case LEFT: return "left";
case RIGHT: return "right";
case TOP: return "top";
case BOTTOM: return "bottom";
case TOP_LEFT: return "top_left";
case TOP_RIGHT: return "top_right";
case BOTTOM_LEFT: return "bottom_left";
case BOTTOM_RIGHT: return "bottom_right";
case CLOSER: return "closer";
case MAXIMIZER: return "maximizer";
case MINIMIZER: return "minimizer";
}
return "";
}
Element(Type type) : type(type) { }
bool operator != (Element const &other) const { return other.type != type; }
bool operator == (Element const &other) const { return other.type == type; }
};
private:
unsigned const _id = 0;
Title _title;
Label _label;
Rect _geometry;
/**
* Window geometry at the start of the current drag operation
*/
Rect _orig_geometry;
/**
* Size as desired by the user during resize drag operations
*/
Area _requested_size;
/**
* Backup of the original geometry while the window is maximized
*/
Rect _unmaximized_geometry;
Rect const _maximized_geometry;
/**
* Window may be partially transparent
*/
bool _has_alpha = false;
/**
* Window is temporarily not visible
*/
bool _is_hidden = false;
bool _is_resizeable = false;
bool _is_maximized = false;
/*
* Number of times the window has been topped. This value is used by
* the decorator to detect the need for bringing the window to the
* front of nitpicker's global view stack even if the stacking order
* stays the same within the decorator instance. This is important in
* the presence of more than a single decorator.
*/
unsigned _topped_cnt = 0;
bool _drag_left_border = false;
bool _drag_right_border = false;
bool _drag_top_border = false;
bool _drag_bottom_border = false;
public:
Window(unsigned id, Rect maximized_geometry)
:
_id(id), _maximized_geometry(maximized_geometry)
{ }
bool has_id(unsigned id) const { return id == _id; }
unsigned id() const { return _id; }
void title(Title const &title) { _title = title; }
void label(Label const &label) { _label = label; }
void geometry(Rect geometry) { _geometry = geometry; }
Point position() const { return _geometry.p1(); }
void position(Point pos) { _geometry = Rect(pos, _geometry.area()); }
void has_alpha(bool has_alpha) { _has_alpha = has_alpha; }
void is_hidden(bool is_hidden) { _is_hidden = is_hidden; }
void is_resizeable(bool is_resizeable) { _is_resizeable = is_resizeable; }
bool label_matches(Label const &label) const { return label == _label; }
/**
* Return true if user drags a window border
*/
bool _drag_border() const
{
return _drag_left_border || _drag_right_border
|| _drag_top_border || _drag_bottom_border;
}
/**
* Define window size
*
* This function is called when the window-list model changes.
*/
void size(Area size)
{
if (_is_maximized) {
_geometry = Rect(_maximized_geometry.p1(), size);
return;
}
if (!_drag_border()) {
_geometry = Rect(_geometry.p1(), size);
return;
}
Point p1 = _geometry.p1(), p2 = _geometry.p2();
if (_drag_left_border)
p1 = Point(p2.x() - size.w() + 1, p1.y());
if (_drag_right_border)
p2 = Point(p1.x() + size.w() - 1, p2.y());
if (_drag_top_border)
p1 = Point(p1.x(), p2.y() - size.h() + 1);
if (_drag_bottom_border)
p2 = Point(p2.x(), p1.y() + size.h() - 1);
_geometry = Rect(p1, p2);
}
Area size() const { return _geometry.area(); }
Area requested_size() const { return _requested_size; }
void serialize(Xml_generator &xml, bool focused, Element highlight)
{
/* omit window from the layout if hidden */
if (_is_hidden)
return;
xml.node("window", [&]() {
xml.attribute("id", _id);
/* present concatenation of label and title in the window's title bar */
{
bool const has_title = Genode::strlen(_title.string()) > 0;
char buf[Label::capacity()];
Genode::snprintf(buf, sizeof(buf), "%s%s%s",
_label.string(),
has_title ? " " : "",
_title.string());
xml.attribute("title", buf);
}
xml.attribute("xpos", _geometry.x1());
xml.attribute("ypos", _geometry.y1());
xml.attribute("width", _geometry.w());
xml.attribute("height", _geometry.h());
xml.attribute("topped", _topped_cnt);
if (focused)
xml.attribute("focused", "yes");
if (highlight.type != Element::UNDEFINED) {
xml.node("highlight", [&] () {
xml.node(highlight.name());
});
}
if (_has_alpha)
xml.attribute("has_alpha", "yes");
if (_is_resizeable) {
xml.attribute("maximizer", "yes");
xml.attribute("closer", "yes");
}
});
}
/**
* Called when the user starts dragging a window element
*/
void initiate_drag_operation(Window::Element element)
{
_drag_left_border = (element.type == Window::Element::LEFT)
|| (element.type == Window::Element::TOP_LEFT)
|| (element.type == Window::Element::BOTTOM_LEFT);
_drag_right_border = (element.type == Window::Element::RIGHT)
|| (element.type == Window::Element::TOP_RIGHT)
|| (element.type == Window::Element::BOTTOM_RIGHT);
_drag_top_border = (element.type == Window::Element::TOP)
|| (element.type == Window::Element::TOP_LEFT)
|| (element.type == Window::Element::TOP_RIGHT);
_drag_bottom_border = (element.type == Window::Element::BOTTOM)
|| (element.type == Window::Element::BOTTOM_LEFT)
|| (element.type == Window::Element::BOTTOM_RIGHT);
_orig_geometry = _geometry;
_requested_size = _geometry.area();
}
void apply_drag_operation(Point offset)
{
if (!_drag_border())
position(_orig_geometry.p1() + offset);
int requested_w = _orig_geometry.w(),
requested_h = _orig_geometry.h();
if (_drag_left_border) requested_w -= offset.x();
if (_drag_right_border) requested_w += offset.x();
if (_drag_top_border) requested_h -= offset.y();
if (_drag_bottom_border) requested_h += offset.y();
_requested_size = Area(max(1, requested_w), max(1, requested_h));
}
void finalize_drag_operation()
{
_requested_size = _geometry.area();
}
void topped() { _topped_cnt++; }
void close() { _requested_size = Area(0, 0); }
bool is_maximized() const { return _is_maximized; }
void is_maximized(bool is_maximized)
{
/* enter maximized state */
if (!_is_maximized && is_maximized) {
_unmaximized_geometry = _geometry;
_requested_size = _maximized_geometry.area();
}
/* leave maximized state */
if (_is_maximized && !is_maximized) {
_requested_size = _unmaximized_geometry.area();
_geometry = Rect(_unmaximized_geometry.p1(), _geometry.area());
}
_is_maximized = is_maximized;
}
};
struct Floating_window_layouter::Main
struct Floating_window_layouter::Main : Operations
{
Signal_receiver &sig_rec;
List<Window> windows;
unsigned hovered_window_id = 0;
unsigned focused_window_id = 0;
unsigned key_cnt = 0;
Window::Element hovered_element = Window::Element::UNDEFINED;
Window::Element hovered_element_now = Window::Element::UNDEFINED;
bool drag_state = false;
bool drag_init_done = true;
Window *lookup_window_by_id(unsigned id)
Window *lookup_window_by_id(Window_id const id)
{
for (Window *w = windows.first(); w; w = w->next())
if (w->has_id(id))
@ -383,6 +73,119 @@ struct Floating_window_layouter::Main
return nullptr;
}
Window const *lookup_window_by_id(Window_id const id) const
{
for (Window const *w = windows.first(); w; w = w->next())
if (w->has_id(id))
return w;
return nullptr;
}
/**
* Apply functor to each window
*
* The functor is called with 'Window const &' as argument.
*/
template <typename FUNC>
void for_each_window(FUNC const &fn) const
{
for (Window const *w = windows.first(); w; w = w->next())
fn(*w);
}
User_state _user_state { *this };
/**************************
** Operations interface **
**************************/
void close(Window_id id) override
{
Window *window = lookup_window_by_id(id);
if (!window)
return;
window->close();
generate_resize_request_model();
generate_focus_model();
}
void to_front(Window_id id) override
{
Window *window = lookup_window_by_id(id);
if (!window)
return;
if (window != windows.first()) {
windows.remove(window);
windows.insert(window);
window->topped();
generate_window_layout_model();
}
}
void focus(Window_id id) override
{
generate_focus_model();
}
void toggle_fullscreen(Window_id id) override
{
/* make sure that the specified window is the front-most one */
to_front(id);
Window *window = lookup_window_by_id(id);
if (!window)
return;
window->is_maximized(!window->is_maximized());
generate_resize_request_model();
}
void drag(Window_id id, Window::Element element, Point clicked, Point curr) override
{
to_front(id);
Window *window = lookup_window_by_id(id);
if (!window)
return;
/*
* The drag operation may result in a change of the window geometry.
* We detect such a change be comparing the original geometry with
* the geometry with the drag operation applied.
*/
Point const last_pos = window->position();
Area const last_requested_size = window->requested_size();
window->drag(element, clicked, curr);
if (last_pos != window->position())
generate_window_layout_model();
if (last_requested_size != window->requested_size())
generate_resize_request_model();
}
void finalize_drag(Window_id id, Window::Element, Point clicked, Point final)
{
Window *window = lookup_window_by_id(id);
if (!window)
return;
window->finalize_drag_operation();
/*
* Update window layout because highlighting may have changed after the
* drag operation. E.g., if the window has not kept up with the
* dragging of a resize handle, the resize handle is no longer hovered.
*/
generate_window_layout_model();
}
/**
* Install handler for responding to window-list changes
@ -420,10 +223,16 @@ struct Floating_window_layouter::Main
Attached_rom_dataspace hover { "hover" };
/**
* Install handler for responding to user input
*/
void handle_input(unsigned);
void handle_input(unsigned)
{
while (input.is_pending())
_user_state.handle_input(input_ds.local_addr<Input::Event>(),
input.flush());
}
Signal_dispatcher<Main> input_dispatcher = {
sig_rec, *this, &Main::handle_input };
@ -440,17 +249,17 @@ struct Floating_window_layouter::Main
Reporter resize_request_reporter = { "resize_request" };
Reporter focus_reporter = { "focus" };
unsigned dragged_window_id = 0;
Point pointer_clicked;
Point pointer_last;
Point pointer_curr;
bool focused_window_is_maximized() const
{
Window const *w = lookup_window_by_id(_user_state.focused_window_id());
return w && w->is_maximized();
}
void import_window_list(Xml_node);
void generate_window_layout_model();
void generate_resize_request_model();
void generate_focus_model();
void initiate_window_drag(Window &window);
/**
* Constructor
@ -553,11 +362,11 @@ void Floating_window_layouter::Main::generate_window_layout_model()
{
for (Window *w = windows.first(); w; w = w->next()) {
bool const is_hovered = w->has_id(hovered_window_id);
bool const is_focused = w->has_id(focused_window_id);
bool const is_hovered = w->has_id(_user_state.hover_state().window_id);
bool const is_focused = w->has_id(_user_state.focused_window_id());
Window::Element const highlight =
is_hovered ? hovered_element : Window::Element::UNDEFINED;
is_hovered ? _user_state.hover_state().element : Window::Element::UNDEFINED;
w->serialize(xml, is_focused, highlight);
}
@ -569,18 +378,17 @@ void Floating_window_layouter::Main::generate_resize_request_model()
{
Reporter::Xml_generator xml(resize_request_reporter, [&] ()
{
Window const *dragged_window = lookup_window_by_id(dragged_window_id);
if (dragged_window) {
for_each_window([&] (Window const &window) {
Area const requested_size = dragged_window->requested_size();
if (requested_size != dragged_window->size()) {
Area const requested_size = window.requested_size();
if (requested_size != window.size()) {
xml.node("window", [&] () {
xml.attribute("id", dragged_window_id);
xml.attribute("id", window.id().value);
xml.attribute("width", requested_size.w());
xml.attribute("height", requested_size.h());
});
}
}
});
});
}
@ -590,7 +398,7 @@ void Floating_window_layouter::Main::generate_focus_model()
Reporter::Xml_generator xml(focus_reporter, [&] ()
{
xml.node("window", [&] () {
xml.attribute("id", focused_window_id);
xml.attribute("id", _user_state.focused_window_id().value);
});
});
}
@ -625,27 +433,10 @@ element_from_hover_model(Genode::Xml_node hover_window_xml)
if (hover_window_xml.has_sub_node("maximizer")) return Type::MAXIMIZER;
if (hover_window_xml.has_sub_node("minimizer")) return Type::MINIMIZER;
return Type::UNDEFINED;
}
void Floating_window_layouter::Main::initiate_window_drag(Window &window)
{
window.initiate_drag_operation(hovered_element);
drag_init_done = true;
/* bring focused window to front */
if (&window != windows.first()) {
windows.remove(&window);
windows.insert(&window);
}
window.topped();
}
void Floating_window_layouter::Main::handle_window_list_update(unsigned)
{
window_list.update();
@ -701,7 +492,7 @@ void Floating_window_layouter::Main::_apply_focus_request()
if (at == nullptr) {
w->topped();
focused_window_id = w->id();
_user_state.focused_window_id(w->id());
generate_focus_model();
}
@ -733,249 +524,33 @@ void Floating_window_layouter::Main::handle_hover_update(unsigned)
try {
Xml_node const hover_xml(hover.local_addr<char>());
Xml_node const hover_window_xml = hover_xml.sub_node("window");
unsigned const id = attribute(hover_window_xml, "id", 0UL);
_user_state.hover(attribute(hover_window_xml, "id", 0UL),
element_from_hover_model(hover_window_xml));
hovered_element_now = element_from_hover_model(hover_window_xml);
}
/*
* An exception may occur during the 'Xml_node' construction if the hover
* model is missing or malformed. Under this condition, we invalidate
* the hover state.
*/
catch (...) {
_user_state.reset_hover();
/*
* Check if we have just received an update while already being in
* dragged state.
*
* This can happen when the user selects a new nitpicker domain by
* clicking on a window decoration. Prior the click, the new session is
* not aware of the current mouse position. So the hover model is not
* up to date. As soon as nitpicker assigns the focus to the new
* session and delivers the corresponding press event, we enter the
* drag state (in the 'handle_input' function. But we don't know which
* window is dragged until the decorator updates the hover model. Now,
* when the model is updated and we are still in dragged state, we can
* finally initiate the window-drag operation for the now-known window.
* Don't generate a focus-model update here. In a situation where the
* pointer has moved over a native nitpicker view (outside the realm of
* the window manager), the hover model as generated by the decorator
* naturally becomes empty. If we posted a focus update, this would
* steal the focus away from the native nitpicker view.
*/
if (id && !drag_init_done && dragged_window_id == 0)
{
dragged_window_id = id;
hovered_window_id = id;
focused_window_id = id;
Window *window = lookup_window_by_id(id);
if (window) {
initiate_window_drag(*window);
generate_window_layout_model();
generate_focus_model();
}
}
if (!drag_state && (id != hovered_window_id || hovered_element_now != hovered_element)) {
hovered_window_id = id;
hovered_element = hovered_element_now;
/* XXX read from config */
bool const focus_follows_pointer = true;
if (id && focus_follows_pointer) {
focused_window_id = id;
generate_focus_model();
}
generate_window_layout_model();
}
} catch (...) {
/* reset focused window if pointer does not hover over any window */
if (!drag_state) {
hovered_element = Window::Element::UNDEFINED;
hovered_window_id = 0;
generate_window_layout_model();
/*
* Don't generate a focus-model update here. In a situation where
* the pointer has moved over a native nitpicker view (outside
* the realm of the window manager), the hover model as generated
* by the decorator naturally becomes empty. If we posted a
* focus update, this would steal the focus away from the native
* nitpicker view.
*/
}
}
}
void Floating_window_layouter::Main::handle_input(unsigned)
{
bool need_regenerate_window_layout_model = false;
bool need_regenerate_resize_request_model = false;
Window *hovered_window = lookup_window_by_id(hovered_window_id);
while (input.is_pending()) {
size_t const num_events = input.flush();
Input::Event const * const ev = input_ds.local_addr<Input::Event>();
for (size_t i = 0; i < num_events; i++) {
Input::Event e = ev[i];
if (e.type() == Input::Event::MOTION
|| e.type() == Input::Event::FOCUS)
pointer_curr = Point(e.ax(), e.ay());
/* track number of pressed buttons/keys */
if (e.type() == Input::Event::PRESS) key_cnt++;
if (e.type() == Input::Event::RELEASE) key_cnt--;
if (e.type() == Input::Event::PRESS
&& e.keycode() == Input::BTN_LEFT) {
/*
* Toggle maximized state
*/
if (hovered_element == Window::Element::MAXIMIZER) {
if (hovered_window) {
dragged_window_id = hovered_window_id;
hovered_window->is_maximized(!hovered_window->is_maximized());
/* bring focused window to front */
if (hovered_window != windows.first()) {
windows.remove(hovered_window);
windows.insert(hovered_window);
}
hovered_window->topped();
focused_window_id = hovered_window_id;
need_regenerate_window_layout_model = true;
need_regenerate_resize_request_model = true;
}
}
bool const hovered_window_is_maximized =
hovered_window ? hovered_window->is_maximized() : false;
/*
* Change window geometry unless the window is in maximized
* state.
*/
if (hovered_element != Window::Element::MAXIMIZER) {
if (!hovered_window_is_maximized) {
drag_state = true;
drag_init_done = false;
dragged_window_id = hovered_window_id;
pointer_clicked = pointer_curr;
pointer_last = pointer_clicked;
/*
* If the hovered window is known at the time of the press
* event, we can initiate the drag operation immediately.
* Otherwise, we the initiation is deferred to the next
* update of the hover model.
*/
if (hovered_window)
initiate_window_drag(*hovered_window);
}
if (hovered_window) {
if (focused_window_id != hovered_window_id) {
focused_window_id = hovered_window_id;
/* bring focused window to front */
if (hovered_window != windows.first()) {
windows.remove(hovered_window);
windows.insert(hovered_window);
}
hovered_window->topped();
generate_focus_model();
}
need_regenerate_window_layout_model = true;
}
}
}
/* detect end of drag operation */
if (e.type() == Input::Event::RELEASE) {
if (key_cnt == 0) {
drag_state = false;
generate_focus_model();
bool const manipulate_geometry =
hovered_element != Window::Element::CLOSER;
Window *dragged_window = lookup_window_by_id(dragged_window_id);
if (dragged_window && manipulate_geometry) {
Area const last_requested_size = dragged_window->requested_size();
dragged_window->finalize_drag_operation();
if (last_requested_size != dragged_window->requested_size())
need_regenerate_resize_request_model = true;
/*
* Update window layout because highlighting may have
* changed after the drag operation. E.g., if the
* window has not kept up with the dragging of a
* resize handle, the resize handle is no longer
* hovered.
*/
}
/**
* Issue resize to 0x0 when releasing the the window closer
*/
if (dragged_window && hovered_element == Window::Element::CLOSER) {
if (hovered_element_now == hovered_element) {
dragged_window->close();
need_regenerate_resize_request_model = true;
}
}
if (dragged_window) {
handle_hover_update(0);
}
}
}
}
}
if (drag_state && (pointer_curr != pointer_last)) {
pointer_last = pointer_curr;
bool const manipulate_geometry =
hovered_element != Window::Element::CLOSER;
Window *dragged_window = lookup_window_by_id(dragged_window_id);
if (dragged_window && manipulate_geometry) {
Point const last_pos = dragged_window->position();
Area const last_requested_size = dragged_window->requested_size();
dragged_window->apply_drag_operation(pointer_curr - pointer_clicked);
if (last_pos != dragged_window->position())
need_regenerate_window_layout_model = true;
if (last_requested_size != dragged_window->requested_size())
need_regenerate_resize_request_model = true;
}
}
if (need_regenerate_window_layout_model)
generate_window_layout_model();
if (need_regenerate_resize_request_model)
generate_resize_request_model();
/* propagate changed hovering to the decorator */
generate_window_layout_model();
}

View File

@ -0,0 +1,33 @@
/*
* \brief Floating window layouter
* \author Norman Feske
* \date 2015-12-31
*/
/*
* Copyright (C) 2015 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _OPERATIONS_H_
#define _OPERATIONS_H_
/* local includes */
#include "window.h"
namespace Floating_window_layouter { struct Operations; }
struct Floating_window_layouter::Operations
{
virtual void close(Window_id) = 0;
virtual void toggle_fullscreen(Window_id) = 0;
virtual void focus(Window_id) = 0;
virtual void to_front(Window_id) = 0;
virtual void drag(Window_id, Window::Element, Point clicked, Point curr) = 0;
virtual void finalize_drag(Window_id, Window::Element, Point clicked, Point final) = 0;
};
#endif /* _OPERATIONS_H_ */

View File

@ -0,0 +1,54 @@
/*
* \brief Floating window layouter
* \author Norman Feske
* \date 2015-12-31
*/
/*
* Copyright (C) 2015 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _TYPES_H_
#define _TYPES_H_
/* Genode includes */
#include <decorator/types.h>
namespace Floating_window_layouter {
using namespace Genode;
typedef Decorator::Point Point;
typedef Decorator::Area Area;
typedef Decorator::Rect Rect;
using Decorator::attribute;
using Decorator::string_attribute;
using Decorator::area_attribute;
using Decorator::point_attribute;
struct Window_id
{
unsigned value = 0;
Window_id() { }
Window_id(unsigned value) : value(value) { }
bool valid() const { return value != 0; }
bool operator != (Window_id const &other) const
{
return other.value != value;
}
bool operator == (Window_id const &other) const
{
return other.value == value;
}
};
}
#endif /* _TYPES_H_ */

View File

@ -0,0 +1,266 @@
/*
* \brief Floating window layouter
* \author Norman Feske
* \date 2013-02-14
*/
/*
* Copyright (C) 2013-2015 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _USER_STATE_H_
#define _USER_STATE_H_
/* local includes */
#include "operations.h"
namespace Floating_window_layouter { class User_state; }
class Floating_window_layouter::User_state
{
public:
struct Hover_state
{
Window_id window_id;
Window::Element element { Window::Element::UNDEFINED };
Hover_state(Window_id id, Window::Element element)
:
window_id(id), element(element)
{ }
};
private:
Window_id _hovered_window_id;
Window_id _focused_window_id;
Window_id _dragged_window_id;
unsigned _key_cnt = 0;
Window::Element _hovered_element = Window::Element::UNDEFINED;
Window::Element _dragged_element = Window::Element::UNDEFINED;
/*
* True while drag operation in progress
*/
bool _drag_state = false;
/*
* False if the hover state (hovered window and element) was not known
* at the initial click of a drag operation. In this case, the drag
* operation is initiated as soon as the hover state becomes known.
*/
bool _drag_init_done = false;
/*
* Pointer position at the beginning of a drag operation
*/
Point _pointer_clicked;
/*
* Current pointer position
*/
Point _pointer_curr;
Operations &_operations;
inline void _handle_event(Input::Event const &);
void _initiate_drag(Window_id hovered_window_id,
Window::Element hovered_element)
{
/*
* This function must never be called without the hover state to be
* defined. This assertion checks this precondition.
*/
if (!hovered_window_id.valid()) {
struct Drag_with_undefined_hover_state { };
throw Drag_with_undefined_hover_state();
}
_drag_init_done = true;
_dragged_window_id = hovered_window_id;
_dragged_element = hovered_element;
/*
* Toggle maximized (fullscreen) state
*/
if (_hovered_element == Window::Element::MAXIMIZER) {
_dragged_window_id = _hovered_window_id;
_focused_window_id = _hovered_window_id;
_operations.toggle_fullscreen(_hovered_window_id);
return;
}
/*
* Bring hovered window to front when clicked
*/
if (_focused_window_id != _hovered_window_id) {
_focused_window_id = _hovered_window_id;
_operations.to_front(_hovered_window_id);
_operations.focus(_hovered_window_id);
}
_operations.drag(_dragged_window_id, _dragged_element,
_pointer_clicked, _pointer_curr);
}
public:
User_state(Operations &operations) : _operations(operations) { }
void handle_input(Input::Event const events[], unsigned num_events)
{
Point const pointer_last = _pointer_curr;
for (size_t i = 0; i < num_events; i++)
_handle_event(events[i]);
/*
* Issue drag operation when in dragged state
*/
if (_drag_state && _drag_init_done && (_pointer_curr != pointer_last))
_operations.drag(_dragged_window_id, _dragged_element,
_pointer_clicked, _pointer_curr);
}
void hover(Window_id window_id, Window::Element element)
{
Window_id const last_hovered_window_id = _hovered_window_id;
_hovered_window_id = window_id;
_hovered_element = element;
/*
* Check if we have just received an update while already being in
* dragged state.
*
* This can happen when the user selects a new nitpicker domain by
* clicking on a window decoration. Prior the click, the new
* session is not aware of the current mouse position. So the hover
* model is not up to date. As soon as nitpicker assigns the focus
* to the new session and delivers the corresponding press event,
* we enter the drag state (in the 'handle_input' function. But we
* don't know which window is dragged until the decorator updates
* the hover model. Now, when the model is updated and we are still
* in dragged state, we can finally initiate the window-drag
* operation for the now-known window.
*/
if (_drag_state && !_drag_init_done && _hovered_window_id.valid())
_initiate_drag(_hovered_window_id, _hovered_element);
/*
* Let focus follows the pointer
*
* XXX obtain policy from config
*/
if (!_drag_state && _hovered_window_id.valid()
&& _hovered_window_id != last_hovered_window_id) {
_focused_window_id = _hovered_window_id;
_operations.focus(_focused_window_id);
}
}
void reset_hover()
{
/* ignore hover resets when in drag state */
if (_drag_state)
return;
_hovered_element = Window::Element::UNDEFINED;
_hovered_window_id = Window_id();
}
Window_id focused_window_id() const { return _focused_window_id; }
void focused_window_id(Window_id id) { _focused_window_id = id; }
Hover_state hover_state() const { return { _hovered_window_id, _hovered_element }; }
};
void Floating_window_layouter::User_state::_handle_event(Input::Event const &e)
{
if (e.type() == Input::Event::MOTION
|| e.type() == Input::Event::FOCUS) {
_pointer_curr = Point(e.ax(), e.ay());
if (_drag_state && _drag_init_done)
_operations.drag(_dragged_window_id, _dragged_element,
_pointer_clicked, _pointer_curr);
}
/* track number of pressed buttons/keys */
if (e.type() == Input::Event::PRESS) _key_cnt++;
if (e.type() == Input::Event::RELEASE) _key_cnt--;
if (e.type() == Input::Event::PRESS
&& e.keycode() == Input::BTN_LEFT
&& _key_cnt == 1) {
/*
* Initiate drag operation if possible
*/
_drag_state = true;
_pointer_clicked = _pointer_curr;
if (_hovered_window_id.valid()) {
/*
* Initiate drag operation
*
* If the hovered window is known at the time of the press event,
* we can initiate the drag operation immediately. Otherwise,
* the initiation is deferred to the next update of the hover
* model.
*/
_initiate_drag(_hovered_window_id, _hovered_element);
} else {
/*
* If the hovering state is undefined at the time of the click,
* we defer the drag handling until the next update of the hover
* state. This intermediate state is captured by '_drag_init_done'.
*/
_drag_init_done = false;
_dragged_window_id = Window_id();
_dragged_element = Window::Element(Window::Element::UNDEFINED);
}
}
/* detect end of drag operation */
if (e.type() == Input::Event::RELEASE
&& _key_cnt == 0
&& _dragged_window_id.valid()) {
_drag_state = false;
/*
* Issue resize to 0x0 when releasing the the window closer
*/
if (_dragged_element == Window::Element::CLOSER) {
if (_dragged_element == _hovered_element)
_operations.close(_dragged_window_id);
}
_operations.finalize_drag(_dragged_window_id, _dragged_element,
_pointer_clicked, _pointer_curr);
}
}
#endif /* _USER_STATE_H_ */

View File

@ -0,0 +1,334 @@
/*
* \brief Floating window layouter
* \author Norman Feske
* \date 2013-02-14
*/
/*
* Copyright (C) 2013-2014 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _WINDOW_H_
#define _WINDOW_H_
/* local includes */
#include "types.h"
namespace Floating_window_layouter { class Window; }
class Floating_window_layouter::Window : public List<Window>::Element
{
public:
typedef String<256> Title;
typedef String<256> Label;
struct Element
{
enum Type { UNDEFINED, TITLE, LEFT, RIGHT, TOP, BOTTOM,
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,
CLOSER, MAXIMIZER, MINIMIZER };
Type type;
char const *name() const
{
switch (type) {
case UNDEFINED: return "";
case TITLE: return "title";
case LEFT: return "left";
case RIGHT: return "right";
case TOP: return "top";
case BOTTOM: return "bottom";
case TOP_LEFT: return "top_left";
case TOP_RIGHT: return "top_right";
case BOTTOM_LEFT: return "bottom_left";
case BOTTOM_RIGHT: return "bottom_right";
case CLOSER: return "closer";
case MAXIMIZER: return "maximizer";
case MINIMIZER: return "minimizer";
}
return "";
}
Element(Type type) : type(type) { }
bool operator != (Element const &other) const { return other.type != type; }
bool operator == (Element const &other) const { return other.type == type; }
};
private:
Window_id const _id;
Title _title;
Label _label;
Rect _geometry;
/**
* Window geometry at the start of the current drag operation
*/
Rect _orig_geometry;
/**
* Size as desired by the user during resize drag operations
*/
Area _requested_size;
/**
* Backup of the original geometry while the window is maximized
*/
Rect _unmaximized_geometry;
Rect const _maximized_geometry;
/**
* Window may be partially transparent
*/
bool _has_alpha = false;
/**
* Window is temporarily not visible
*/
bool _is_hidden = false;
bool _is_resizeable = false;
bool _is_maximized = false;
bool _is_dragged = false;
/*
* Number of times the window has been topped. This value is used by
* the decorator to detect the need for bringing the window to the
* front of nitpicker's global view stack even if the stacking order
* stays the same within the decorator instance. This is important in
* the presence of more than a single decorator.
*/
unsigned _topped_cnt = 0;
bool _drag_left_border = false;
bool _drag_right_border = false;
bool _drag_top_border = false;
bool _drag_bottom_border = false;
/**
* Called when the user starts dragging a window element
*/
void _initiate_drag_operation(Window::Element element)
{
_drag_left_border = (element.type == Window::Element::LEFT)
|| (element.type == Window::Element::TOP_LEFT)
|| (element.type == Window::Element::BOTTOM_LEFT);
_drag_right_border = (element.type == Window::Element::RIGHT)
|| (element.type == Window::Element::TOP_RIGHT)
|| (element.type == Window::Element::BOTTOM_RIGHT);
_drag_top_border = (element.type == Window::Element::TOP)
|| (element.type == Window::Element::TOP_LEFT)
|| (element.type == Window::Element::TOP_RIGHT);
_drag_bottom_border = (element.type == Window::Element::BOTTOM)
|| (element.type == Window::Element::BOTTOM_LEFT)
|| (element.type == Window::Element::BOTTOM_RIGHT);
_orig_geometry = _geometry;
_requested_size = _geometry.area();
_is_dragged = true;
}
/**
* Called each time the pointer moves while the window is dragged
*/
void _apply_drag_operation(Point offset)
{
if (!_drag_border())
position(_orig_geometry.p1() + offset);
int requested_w = _orig_geometry.w(),
requested_h = _orig_geometry.h();
if (_drag_left_border) requested_w -= offset.x();
if (_drag_right_border) requested_w += offset.x();
if (_drag_top_border) requested_h -= offset.y();
if (_drag_bottom_border) requested_h += offset.y();
_requested_size = Area(max(1, requested_w), max(1, requested_h));
}
/**
* Return true if user drags a window border
*/
bool _drag_border() const
{
return _drag_left_border || _drag_right_border
|| _drag_top_border || _drag_bottom_border;
}
public:
Window(Window_id id, Rect maximized_geometry)
:
_id(id), _maximized_geometry(maximized_geometry)
{ }
bool has_id(Window_id id) const { return id == _id; }
Window_id id() const { return _id; }
void title(Title const &title) { _title = title; }
void label(Label const &label) { _label = label; }
void geometry(Rect geometry) { _geometry = geometry; }
Point position() const { return _geometry.p1(); }
void position(Point pos) { _geometry = Rect(pos, _geometry.area()); }
void has_alpha(bool has_alpha) { _has_alpha = has_alpha; }
void is_hidden(bool is_hidden) { _is_hidden = is_hidden; }
void is_resizeable(bool is_resizeable) { _is_resizeable = is_resizeable; }
bool label_matches(Label const &label) const { return label == _label; }
/**
* Define window size
*
* This function is called when the window-list model changes.
*/
void size(Area size)
{
if (_is_maximized) {
_geometry = Rect(_maximized_geometry.p1(), size);
return;
}
if (!_drag_border()) {
_geometry = Rect(_geometry.p1(), size);
return;
}
Point p1 = _geometry.p1(), p2 = _geometry.p2();
if (_drag_left_border)
p1 = Point(p2.x() - size.w() + 1, p1.y());
if (_drag_right_border)
p2 = Point(p1.x() + size.w() - 1, p2.y());
if (_drag_top_border)
p1 = Point(p1.x(), p2.y() - size.h() + 1);
if (_drag_bottom_border)
p2 = Point(p2.x(), p1.y() + size.h() - 1);
_geometry = Rect(p1, p2);
}
Area size() const { return _geometry.area(); }
Area requested_size() const { return _requested_size; }
void serialize(Xml_generator &xml, bool focused, Element highlight)
{
/* omit window from the layout if hidden */
if (_is_hidden)
return;
xml.node("window", [&]() {
xml.attribute("id", _id.value);
/* present concatenation of label and title in the window's title bar */
{
bool const has_title = Genode::strlen(_title.string()) > 0;
char buf[Label::capacity()];
Genode::snprintf(buf, sizeof(buf), "%s%s%s",
_label.string(),
has_title ? " " : "",
_title.string());
xml.attribute("title", buf);
}
xml.attribute("xpos", _geometry.x1());
xml.attribute("ypos", _geometry.y1());
xml.attribute("width", _geometry.w());
xml.attribute("height", _geometry.h());
xml.attribute("topped", _topped_cnt);
if (focused)
xml.attribute("focused", "yes");
if (highlight.type != Element::UNDEFINED) {
xml.node("highlight", [&] () {
xml.node(highlight.name());
});
}
if (_has_alpha)
xml.attribute("has_alpha", "yes");
if (_is_resizeable) {
xml.attribute("maximizer", "yes");
xml.attribute("closer", "yes");
}
});
}
void drag(Window::Element element, Point clicked, Point curr)
{
/* prevent maximized windows from being dragged */
if (is_maximized())
return;
if (!_is_dragged)
_initiate_drag_operation(element);
_apply_drag_operation(curr - clicked);
}
void finalize_drag_operation()
{
_requested_size = _geometry.area();
_is_dragged = false;
}
void topped() { _topped_cnt++; }
void close() { _requested_size = Area(0, 0); }
bool is_maximized() const { return _is_maximized; }
void is_maximized(bool is_maximized)
{
/* enter maximized state */
if (!_is_maximized && is_maximized) {
_unmaximized_geometry = _geometry;
_requested_size = _maximized_geometry.area();
}
/* leave maximized state */
if (_is_maximized && !is_maximized) {
_requested_size = _unmaximized_geometry.area();
_geometry = Rect(_unmaximized_geometry.p1(), _geometry.area());
}
_is_maximized = is_maximized;
}
};
#endif /* _WINDOW_H_ */