Files
genode/repos/gems/src/app/window_layouter/window.h
Norman Feske c629c54153 Make util/geometry.h C++20 friendly
- Move header to base/include to make it applicable for base types
  like 'Affinity' down the road.
- Represent 'Rect' as typle of point and area, which is the most
  common form of initialization, creates in valid 'Rect' by default.
- Turn Point, Area, and Rect into compound types, making x, y, w, h, at,
  area accessible without a method call
- 'Rect::Compound' function for constructing a 'Rect' from two points,
  replacing a former constructor
- Use result type 'Rect::Cut_remainder' instead of out parameters.

Fixes #5239
2024-06-20 12:54:30 +02:00

521 lines
13 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 _WINDOW_H_
#define _WINDOW_H_
/* Genode includes */
#include <util/list_model.h>
/* local includes */
#include <types.h>
#include <focus_history.h>
#include <assign.h>
#include <decorator_margins.h>
namespace Window_layouter { class Window; }
class Window_layouter::Window : public List_model<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; }
bool resize_handle() const
{
return type == LEFT || type == RIGHT
|| type == TOP || type == BOTTOM
|| type == TOP_LEFT || type == TOP_RIGHT
|| type == BOTTOM_LEFT || type == BOTTOM_RIGHT
|| type == MAXIMIZER;
}
};
private:
Window_id const _id;
Title _title { };
Label const _label;
Decorator_margins const &_decorator_margins;
Rect _geometry { };
/**
* Window geometry at the start of the current drag operation
*/
Rect _orig_geometry { };
/**
* Destined window geometry as defined by the user's drag operation
*/
Rect _drag_geometry { };
Area _client_size;
/**
* Size as desired by the user during resize drag operations
*/
Area _dragged_size;
/**
* Target geometry the window is assigned to, used while maximized
*/
Rect _target_geometry { };
/**
* Desired size to be requested to the client
*/
Area _requested_size() const
{
return (_maximized || !_floating)
? _decorator_margins.inner_geometry(_target_geometry).area
: _dragged_size;
}
/**
* Most recent resize request propagated to the window manager
*
* Initially, no resize request must be generated because the
* '_dragged_size' corresponds to the window size.
*/
Area _reported_resize_request = _dragged_size;
/**
* Window may be partially transparent
*/
bool _has_alpha = false;
/**
* Window is temporarily not visible
*/
bool _hidden = false;
bool _resizeable = false;
/**
* Toggled interactively
*/
bool _maximized = false;
/**
* Set when position is defined in the window's assign rule
*/
bool _floating = false;
bool _use_target_area() const { return _maximized || !_floating; }
bool _dragged = false;
Element _dragged_element { Element::UNDEFINED };
bool _focused = false;
Element _hovered { Element::UNDEFINED };
/*
* Value that keeps track when the window has been moved to front
* most recently. It is used as a criterion for the order of the
* generated <assign> rules.
*
* This value is also 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 _to_front_cnt = 0;
Focus_history::Entry _focus_history_entry;
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)
{
_dragged_element = element;
if (_resizeable) {
_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;
_drag_geometry = _geometry;
_dragged_size = _geometry.area;
_dragged = true;
}
/**
* Called each time the pointer moves while the window is dragged
*/
void _apply_drag_operation(Point offset)
{
/* move window */
if (!_drag_border()) {
_drag_geometry = Rect(_orig_geometry.p1() + offset,
_orig_geometry.area);
return;
}
/* resize window */
int x1 = _orig_geometry.x1(), y1 = _orig_geometry.y1(),
x2 = _orig_geometry.x2(), y2 = _orig_geometry.y2();
if (_drag_left_border) x1 = min(x1 + offset.x, x2);
if (_drag_right_border) x2 = max(x2 + offset.x, x1);
if (_drag_top_border) y1 = min(y1 + offset.y, y2);
if (_drag_bottom_border) y2 = max(y2 + offset.y, y1);
_drag_geometry = Rect::compound(Point(x1, y1), Point(x2, y2));
_dragged_size = _drag_geometry.area;
}
/**
* 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;
}
Constructible<Assign::Member> _assign_member { };
public:
Window(Window_id id, Label const &label, Area initial_size,
Focus_history &focus_history,
Decorator_margins const &decorator_margins)
:
_id(id), _label(label),
_decorator_margins(decorator_margins),
_client_size(initial_size),
_dragged_size(initial_size),
_focus_history_entry(focus_history, _id)
{ }
bool has_id(Window_id id) const { return id == _id; }
Window_id id() const { return _id; }
void title(Title const &title) { _title = title; }
Label label() const { return _label; }
bool dragged() const { return _dragged; }
Rect effective_inner_geometry() const
{
if (!_dragged)
return _geometry;
int x1 = _orig_geometry.x1(), y1 = _orig_geometry.y1(),
x2 = _orig_geometry.x2(), y2 = _orig_geometry.y2();
/* move window */
if (!_drag_border())
return _drag_geometry;
/* resize window */
if (_drag_left_border) x1 = x2 - _client_size.w + 1;
if (_drag_right_border) x2 = x1 + _client_size.w - 1;
if (_drag_top_border) y1 = y2 - _client_size.h + 1;
if (_drag_bottom_border) y2 = y1 + _client_size.h - 1;
return Rect::compound(Point(x1, y1), Point(x2, y2));
}
/**
* Place window
*
* \param geometry outer geometry available to the window
*/
void outer_geometry(Rect outer)
{
/* drop attempts to apply layout while dragging the window */
if (_dragged)
return;
_geometry = _decorator_margins.inner_geometry(outer);
_dragged_size = _geometry.area;
}
Rect outer_geometry() const
{
return _decorator_margins.outer_geometry(_geometry);
}
Area client_size() const { return _client_size; }
void focused(bool focused) { _focused = focused; }
void hovered(Element hovered) { _hovered = hovered; }
Point position() const { return _geometry.p1(); }
void has_alpha(bool has_alpha) { _has_alpha = has_alpha; }
void hidden(bool hidden) { _hidden = hidden; }
void resizeable(bool resizeable) { _resizeable = 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 client_size(Area size) { _client_size = size; }
/**
* Return true if a request request to the window manager is due
*/
bool resize_request_needed() const
{
/* a resize request for the current size is already in flight */
if (_requested_size() == _reported_resize_request)
return false;
return (_requested_size() != _client_size);
}
/**
* Mark the currently requested size as processed so that no further
* resize requests for the same size are generated
*/
void resize_request_updated() { _reported_resize_request = _requested_size(); }
void gen_resize_request(Xml_generator &xml) const
{
Area const size = _requested_size();
if (size == _client_size)
return;
xml.node("window", [&] () {
xml.attribute("id", _id.value);
xml.attribute("width", size.w);
xml.attribute("height", size.h);
});
}
void generate(Xml_generator &xml) const
{
/* omit window from the layout if hidden */
if (_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 = strlen(_title.string()) > 0;
String<Label::capacity()> const
title(_label, (has_title ? " " : ""), _title);
xml.attribute("title", title);
}
Rect const rect = _use_target_area()
? _decorator_margins.inner_geometry(_target_geometry)
: effective_inner_geometry();
xml.attribute("xpos", rect.x1());
xml.attribute("ypos", rect.y1());
/*
* Constrain size of non-floating windows
*
* In contrast to a tiled or maximized window that is hard
* constrained by the size of the target geometry even if the
* client requests a larger size, floating windows respect the
* size defined by the client.
*
* This way, while floating, applications are able to resize
* their window according to the application's state. For
* example, a bottom-right resize corner of a Qt application is
* handled by the application, not the window manager, but it
* should still work as expected by the user. Note that the
* ability of the application to influence the layout is
* restricted to its window size. The window position cannot be
* influenced. This limits the ability of an application to
* impersonate other applications.
*/
Area const size = _use_target_area()
? Area(min(rect.w(), _client_size.w),
min(rect.h(), _client_size.h))
: _client_size;
xml.attribute("width", size.w);
xml.attribute("height", size.h);
if (_focused)
xml.attribute("focused", "yes");
if (_dragged) {
xml.node("highlight", [&] () {
xml.node(_dragged_element.name(), [&] () {
xml.attribute("pressed", "yes"); }); });
} else {
bool const passive = (!_resizeable && _hovered.resize_handle())
|| (_hovered.type == Element::UNDEFINED);
if (!passive)
xml.node("highlight", [&] () {
xml.node(_hovered.name()); });
}
if (_has_alpha)
xml.attribute("has_alpha", "yes");
if (_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 (maximized())
return;
if (!_dragged)
_initiate_drag_operation(element);
_apply_drag_operation(curr - clicked);
}
void finalize_drag_operation()
{
_drag_left_border = false;
_drag_right_border = false;
_drag_top_border = false;
_drag_bottom_border = false;
_geometry = effective_inner_geometry();
_dragged_size = _geometry.area;
_dragged = false;
}
void to_front_cnt(unsigned to_front_cnt) { _to_front_cnt = to_front_cnt; }
unsigned to_front_cnt() const { return _to_front_cnt; }
void close() { _dragged_size = Area(0, 0); }
void target_geometry(Rect rect) { _target_geometry = rect; }
bool maximized() const { return _maximized; }
void maximized(bool maximized) { _maximized = maximized; }
void floating(bool floating) { _floating = floating; }
void dissolve_from_assignment() { _assign_member.destruct(); }
/**
* Associate window with a <assign> definition
*/
void assignment(Registry<Assign::Member> &registry)
{
/* retain first matching assignment only */
if (_assign_member.constructed())
return;
_assign_member.construct(registry, *this);
}
/**
* List_model::Element
*/
bool matches(Xml_node const &node) const
{
return node.attribute_value("id", 0U) == _id;
}
/**
* List_model::Element
*/
static bool type_matches(Xml_node const &) { return true; }
};
#endif /* _WINDOW_H_ */