346 lines
8.9 KiB
C++
346 lines
8.9 KiB
C++
/*
|
|
* \brief Window layouter
|
|
* \author Norman Feske
|
|
* \date 2013-02-14
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2013-2017 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
*/
|
|
|
|
#ifndef _USER_STATE_H_
|
|
#define _USER_STATE_H_
|
|
|
|
/* local includes */
|
|
#include "operations.h"
|
|
#include "key_sequence_tracker.h"
|
|
|
|
namespace Window_layouter { class User_state; }
|
|
|
|
|
|
class 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)
|
|
{ }
|
|
|
|
bool operator != (Hover_state const &other) const
|
|
{
|
|
return window_id != other.window_id
|
|
|| element != other.element;
|
|
}
|
|
};
|
|
|
|
private:
|
|
|
|
Window_id _hovered_window_id { };
|
|
Window_id _focused_window_id { };
|
|
Window_id _dragged_window_id { };
|
|
|
|
unsigned _key_cnt = 0;
|
|
|
|
Key_sequence_tracker _key_sequence_tracker { };
|
|
|
|
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;
|
|
|
|
Focus_history &_focus_history;
|
|
|
|
/*
|
|
* Return true if key is potentially part of a key sequence
|
|
*/
|
|
static bool _key(Input::Keycode key) { return key != Input::BTN_LEFT; }
|
|
|
|
bool _key(Input::Event const &ev) const
|
|
{
|
|
bool relevant = false;
|
|
|
|
ev.handle_press([&] (Input::Keycode key, Codepoint) {
|
|
relevant |= _key(key); });
|
|
|
|
ev.handle_release([&] (Input::Keycode key) {
|
|
relevant |= _key(key); });
|
|
|
|
return relevant;
|
|
}
|
|
|
|
inline void _handle_event(Input::Event const &, Xml_node);
|
|
|
|
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;
|
|
_focus_history.focus(_focused_window_id);
|
|
|
|
_operations.toggle_fullscreen(_hovered_window_id);
|
|
|
|
_hovered_element = Window::Element::UNDEFINED;
|
|
_hovered_window_id = Window_id();
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Bring hovered window to front when clicked
|
|
*/
|
|
if (_focused_window_id != _hovered_window_id) {
|
|
|
|
_focused_window_id = _hovered_window_id;
|
|
_focus_history.focus(_focused_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, Focus_history &focus_history)
|
|
:
|
|
_operations(operations), _focus_history(focus_history)
|
|
{ }
|
|
|
|
void handle_input(Input::Event const events[], unsigned num_events,
|
|
Xml_node const &config)
|
|
{
|
|
Point const pointer_last = _pointer_curr;
|
|
|
|
for (size_t i = 0; i < num_events; i++)
|
|
_handle_event(events[i], config);
|
|
|
|
/*
|
|
* 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;
|
|
_focus_history.focus(_focused_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 Window_layouter::User_state::_handle_event(Input::Event const &e,
|
|
Xml_node config)
|
|
{
|
|
e.handle_absolute_motion([&] (int x, int y) {
|
|
_pointer_curr = Point(x, y); });
|
|
|
|
if (e.absolute_motion() || e.focus_enter()) {
|
|
|
|
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.press()) _key_cnt++;
|
|
if (e.release()) _key_cnt--;
|
|
|
|
/* handle pointer click */
|
|
if (e.key_press(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.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);
|
|
}
|
|
|
|
/* handle key sequences */
|
|
if (_key(e)) {
|
|
|
|
if (e.press() && _key_cnt == 1)
|
|
_key_sequence_tracker.reset();
|
|
|
|
_key_sequence_tracker.apply(e, config, [&] (Action action) {
|
|
|
|
switch (action.type()) {
|
|
|
|
case Action::TOGGLE_FULLSCREEN:
|
|
_operations.toggle_fullscreen(_focused_window_id);
|
|
return;
|
|
|
|
case Action::RAISE_WINDOW:
|
|
_operations.to_front(_focused_window_id);
|
|
return;
|
|
|
|
case Action::NEXT_WINDOW:
|
|
_focused_window_id = _focus_history.next(_focused_window_id);
|
|
_operations.focus(_focused_window_id);
|
|
return;
|
|
|
|
case Action::PREV_WINDOW:
|
|
_focused_window_id = _focus_history.prev(_focused_window_id);
|
|
_operations.focus(_focused_window_id);
|
|
return;
|
|
|
|
case Action::SCREEN:
|
|
_operations.screen(action.target_name());
|
|
return;
|
|
|
|
default:
|
|
warning("action ", (int)action.type(), " unhanded");
|
|
}
|
|
});
|
|
}
|
|
|
|
/* update focus history after key/button action is completed */
|
|
if (e.release() && _key_cnt == 0)
|
|
_focus_history.focus(_focused_window_id);
|
|
}
|
|
|
|
#endif /* _USER_STATE_H_ */
|