window layouter: improved maximize handling

It turns out that the commit "window layouter: allow floating apps to
resize" interplays badly with the interactive toggling of the maximize
state of windows. In contrast to the window geometry and stacking, which
is always updated through the rules-feedback mechanism, interactive
changes of the maximize state omitted this loop and instead took a local
shortcut. Because of this shortcut, the maximized geometry eventually
ended up as window size in the window's assign rule. So unmaximizing the
window failed to revert the geometry to its original state.

This patch removes this inconsistency. The maximize state adheres to the
official chain of commands through the rules mechanism now. The state is
now maintained internally without affecting the window's geometry and is
evaluated while generating the window layout only.

As a minor loosely related improvement, this patch prevents the
highlighting of resize handles for non-resizable windows.

Issue #3200
This commit is contained in:
Norman Feske 2019-03-16 23:50:09 +01:00 committed by Christian Helmuth
parent 104def8e51
commit 806dc5de36
3 changed files with 124 additions and 71 deletions

View File

@ -101,34 +101,15 @@ class Window_layouter::Assign : public List_model<Assign>::Element
Rect window_geometry(unsigned win_id, Area client_size, Rect target_geometry,
Decorator_margins const &decorator_margins) const
{
bool const floating = !_maximized && _pos_defined;
if (!floating)
if (!_pos_defined)
return target_geometry;
/*
* Position floating window
*
* 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.
*/
Point const any_pos(150*win_id % 800, 30 + (100*win_id % 500));
Point const pos(_xpos_any ? any_pos.x() : _pos.x(),
_ypos_any ? any_pos.y() : _pos.y());
Rect const inner(pos, client_size);
Rect const inner(pos, _size_defined ? _size : client_size);
Rect const outer = decorator_margins.outer_geometry(inner);
return Rect(outer.p1() + target_geometry.p1(), outer.area());
@ -222,7 +203,28 @@ class Window_layouter::Assign : public List_model<Assign>::Element
}
if (_maximized)
xml.attribute("maximized", true);
xml.attribute("maximized", "yes");
}
struct Window_state
{
Rect geometry;
bool maximized;
};
void gen_geometry_attr(Xml_generator &xml, Window_state const &window) const
{
Rect const rect = window.maximized ? Rect(_pos, _size) : window.geometry;
if (_pos_defined) {
xml.attribute("xpos", rect.x1());
xml.attribute("ypos", rect.y1());
xml.attribute("width", rect.w());
xml.attribute("height", rect.h());
}
if (window.maximized)
xml.attribute("maximized", "yes");
}
template <typename FN>

View File

@ -108,9 +108,10 @@ struct Window_layouter::Main : Operations,
assign.for_each_member([&] (Assign::Member &member) {
Rect const rect = member.window.maximized()
? target.geometry()
: assign.window_geometry(member.window.id().value,
member.window.floating(assign.floating());
member.window.target_geometry(target.geometry());
Rect const rect = assign.window_geometry(member.window.id().value,
member.window.client_size(),
target.geometry(),
_decorator_margins);
@ -212,7 +213,7 @@ struct Window_layouter::Main : Operations,
_window_list.with_window(id, [&] (Window &window) {
window.maximized(!window.maximized()); });
_update_window_layout();
_gen_rules();
_gen_resize_request();
}
@ -422,9 +423,10 @@ void Window_layouter::Main::_gen_resize_request()
return;
bool resize_needed = false;
_window_list.for_each_window([&] (Window const &window) {
if (window.resize_request_needed())
resize_needed = true; });
_assign_list.for_each([&] (Assign const &assign) {
assign.for_each_member([&] (Assign::Member const &member) {
if (member.window.resize_request_needed())
resize_needed = true; }); });
if (!resize_needed)
return;
@ -455,10 +457,8 @@ void Window_layouter::Main::_gen_rules_assignments(Xml_generator &xml, FN const
if (!assign.floating())
return;
if (window.maximized())
assign.gen_geometry_attr(xml);
else
window.gen_inner_geometry(xml);
assign.gen_geometry_attr(xml, { .geometry = window.effective_inner_geometry(),
.maximized = window.maximized() });
};
/* turn wildcard assignments into exact assignments */

View File

@ -65,6 +65,15 @@ class Window_layouter::Window : public List_model<Window>::Element
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:
@ -94,15 +103,30 @@ class Window_layouter::Window : public List_model<Window>::Element
/**
* Size as desired by the user during resize drag operations
*/
Area _requested_size;
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
* '_requested_size' corresponds to the window size.
* '_dragged_size' corresponds to the window size.
*/
Area _reported_resize_request = _requested_size;
Area _reported_resize_request = _dragged_size;
/**
* Window may be partially transparent
@ -116,8 +140,18 @@ class Window_layouter::Window : public List_model<Window>::Element
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 };
@ -172,7 +206,7 @@ class Window_layouter::Window : public List_model<Window>::Element
_orig_geometry = _geometry;
_drag_geometry = _geometry;
_requested_size = _geometry.area();
_dragged_size = _geometry.area();
_dragged = true;
}
@ -200,7 +234,7 @@ class Window_layouter::Window : public List_model<Window>::Element
_drag_geometry = Rect(Point(x1, y1), Point(x2, y2));
_requested_size = _drag_geometry.area();
_dragged_size = _drag_geometry.area();
}
/**
@ -223,7 +257,7 @@ class Window_layouter::Window : public List_model<Window>::Element
_id(id), _label(label),
_decorator_margins(decorator_margins),
_client_size(initial_size),
_requested_size(initial_size),
_dragged_size(initial_size),
_focus_history_entry(focus_history, _id)
{ }
@ -271,7 +305,7 @@ class Window_layouter::Window : public List_model<Window>::Element
_geometry = _decorator_margins.inner_geometry(outer);
_requested_size = _geometry.area();
_dragged_size = _geometry.area();
}
Rect outer_geometry() const
@ -302,49 +336,35 @@ class Window_layouter::Window : public List_model<Window>::Element
*/
void client_size(Area size) { _client_size = size; }
/*
* Called for generating the 'rules' report
*/
void gen_inner_geometry(Xml_generator &xml) const
{
Rect const inner = effective_inner_geometry();
xml.attribute("xpos", inner.x1());
xml.attribute("ypos", inner.y1());
xml.attribute("width", inner.w());
xml.attribute("height", inner.h());
if (_maximized)
xml.attribute("maximized", true);
}
/**
* 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)
if (_requested_size() == _reported_resize_request)
return false;
return (_requested_size != _client_size);
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 resize_request_updated() { _reported_resize_request = _requested_size(); }
void gen_resize_request(Xml_generator &xml) const
{
if (_requested_size == _client_size)
Area const size = _requested_size();
if (size == _client_size)
return;
xml.node("window", [&] () {
xml.attribute("id", _id.value);
xml.attribute("width", _requested_size.w());
xml.attribute("height", _requested_size.h());
xml.attribute("width", size.w());
xml.attribute("height", size.h());
});
}
@ -368,12 +388,38 @@ class Window_layouter::Window : public List_model<Window>::Element
xml.attribute("title", title);
}
Rect const rect = effective_inner_geometry();
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());
xml.attribute("width", min(rect.w(), _client_size.w()));
xml.attribute("height", min(rect.h(), _client_size.h()));
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");
@ -386,7 +432,9 @@ class Window_layouter::Window : public List_model<Window>::Element
} else {
if (_hovered.type != Element::UNDEFINED)
bool const passive = (!_resizeable && _hovered.resize_handle())
|| (_hovered.type == Element::UNDEFINED);
if (!passive)
xml.node("highlight", [&] () {
xml.node(_hovered.name()); });
}
@ -420,20 +468,23 @@ class Window_layouter::Window : public List_model<Window>::Element
_drag_right_border = false;
_drag_top_border = false;
_drag_bottom_border = false;
_requested_size = effective_inner_geometry().area();
_dragged_size = effective_inner_geometry().area();
}
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() { _requested_size = Area(0, 0); }
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(); }
/**