refactoring tiling window manager

Laying out the windows in the window manager frame, is a very similar
process regardless of doing it horizontally or vertically. So the
template design pattern was applied and the general functionality was
pulled up; only specific functionalities were put into the corresponding
classes.

kNCursesDemoApp: Separated the screen into three windows vertically. The
top and bottom window contain another two windows each, lay out
horizontally. The top left window is fixed to width 1 and the top right
takes the rest of the space. Bottom two windows take 50 % of the space
each.

Remarks: There are still some rendering bugs, when hiding and showing
windows. E. g. when hiding the top or middle window with `<F1>` or
`<F2>` respectively, the bottom window has some errors. Maybe some
timing issue.
This commit is contained in:
Christian Burger 2022-05-18 21:15:13 +02:00
parent 0a086ff604
commit 3be5336ca0
8 changed files with 182 additions and 96 deletions

View file

@ -37,19 +37,32 @@ namespace krikkel::NCurses
= new VerticalTilingWindowManager(Root_Window, &ncursesMutex); = new VerticalTilingWindowManager(Root_Window, &ncursesMutex);
topWindowManager topWindowManager
= new HorizontalTilingWindowManager(new Window(), &ncursesMutex); = new HorizontalTilingWindowManager(new Window(), &ncursesMutex);
bottomWindowManager
= new HorizontalTilingWindowManager(new Window(), &ncursesMutex);
windowManager->addWindow(topWindowManager); windowManager->addWindow(topWindowManager);
ptyWindow = new PtyWindow(&ncursesMutex, 0, 0, 0, 0); ptyWindow = new PtyWindow(&ncursesMutex, 0, 0, 0, 0);
windowManager->addWindow(ptyWindow); windowManager->addWindow(ptyWindow);
dummyWindowBottom = new Window(windowManager); windowManager->addWindow(bottomWindowManager);
windowManager->updateLayout(); windowManager->updateLayout();
windowManager->refresh(); windowManager->refresh();
dummyWindowTopLeft = new Window(topWindowManager); dummyWindowTopLeft = new Window();
topWindowManager->addWindow(dummyWindowTopLeft, 1);
dummyWindowTopLeft->addstr("t\no\np\n \nl\ne\nf\nt\n w\ni\nn\nd\no\nw");
dummyWindowTopRight = new Window(topWindowManager); dummyWindowTopRight = new Window(topWindowManager);
dummyWindowTopRight->addstr("top right window");
topWindowManager->updateLayout(); topWindowManager->updateLayout();
topWindowManager->refresh(); topWindowManager->refresh();
dummyWindowBottomLeft = new Window(bottomWindowManager);
dummyWindowBottomLeft->addstr("bottom left window");
dummyWindowBottomRight = new Window(bottomWindowManager);
dummyWindowBottomRight->addstr("bottom right window");
bottomWindowManager->updateLayout();
bottomWindowManager->refresh();
} }
void DemoApp::forkShell() void DemoApp::forkShell()
@ -110,10 +123,10 @@ namespace krikkel::NCurses
windowManager->refresh(); windowManager->refresh();
break; break;
case KEY_F(3): case KEY_F(3):
if(dummyWindowBottom->isHidden()) if(bottomWindowManager->isHidden())
windowManager->showWindow(dummyWindowBottom); windowManager->showWindow(bottomWindowManager);
else else
windowManager->hideWindow(dummyWindowBottom); windowManager->hideWindow(bottomWindowManager);
windowManager->updateLayout(); windowManager->updateLayout();
windowManager->refresh(); windowManager->refresh();
break; break;

View file

@ -23,9 +23,9 @@ namespace krikkel::NCurses
private: private:
VerticalTilingWindowManager *windowManager; VerticalTilingWindowManager *windowManager;
HorizontalTilingWindowManager *topWindowManager; HorizontalTilingWindowManager *topWindowManager, *bottomWindowManager;
Window *dummyWindowBottom, *dummyWindowTopLeft, Window *dummyWindowTopLeft, *dummyWindowTopRight
*dummyWindowTopRight; , *dummyWindowBottomLeft, *dummyWindowBottomRight;
PtyWindow *ptyWindow; PtyWindow *ptyWindow;
std::recursive_mutex ncursesMutex; std::recursive_mutex ncursesMutex;

View file

@ -2,6 +2,7 @@
* @author Christian Burger (christian@krikkel.de) * @author Christian Burger (christian@krikkel.de)
*/ */
#include "Debug.hpp"
#include <kNCurses/HorizontalTilingWindowManager.hpp> #include <kNCurses/HorizontalTilingWindowManager.hpp>
#include <kNCurses/Window.hpp> #include <kNCurses/Window.hpp>
#include <ncursesw/ncurses.h> #include <ncursesw/ncurses.h>
@ -9,45 +10,31 @@
namespace krikkel::NCurses namespace krikkel::NCurses
{ {
using std::list;
using std::recursive_mutex; using std::recursive_mutex;
using std::scoped_lock;
HorizontalTilingWindowManager::HorizontalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex) HorizontalTilingWindowManager::HorizontalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex)
: TilingWindowManager(rootWindow, ncursesMutex) : TilingWindowManager(rootWindow, ncursesMutex)
{} {}
void HorizontalTilingWindowManager::updateLayout() void HorizontalTilingWindowManager::resizeAndMoveWindow(Window *window, windowDimension dimension, windowPosition position)
{ {
scoped_lock lock(*ncursesMutex); __debug_log("(" + std::to_string(position + begx()) + ", " + std::to_string(0 + begy()) + ", " + std::to_string(dimension) + ", " + std::to_string(height()) + ")");
window->resize(height(), dimension);
size_t stackSize = visibleStack.size(); window->mvwin(0 + begy(), position + begx());
if(stackSize == 0)
{
clear();
return;
} }
int availableWidth = width(); TilingWindowManager::windowDimension HorizontalTilingWindowManager::getAvailableSpace()
int availableHeight = height();
int windowWidth = availableWidth / stackSize - 1;
if((windowWidth + 1) * stackSize < availableWidth)
++windowWidth;
uint16_t x = 0;
list<Window *>::iterator it = visibleStack.begin();
for(size_t index = 0
; index < visibleStack.size() - 1
; ++index)
{ {
resizeWindow(*it++, availableHeight, windowWidth, 0, x); return width();
x += windowWidth; }
void HorizontalTilingWindowManager::windowBorder()
{ {
move(0, x++); vline(height());
vline(availableHeight);
} }
}
resizeWindow(*it, availableHeight, availableWidth - x, 0, x); void HorizontalTilingWindowManager::moveCursor(TilingWindowManager::windowPosition position)
{
move(0, position);
} }
} }

View file

@ -4,12 +4,15 @@
#include <kNCurses/TilingWindowManager.hpp> #include <kNCurses/TilingWindowManager.hpp>
#include <kNCurses/Window.hpp> #include <kNCurses/Window.hpp>
#include "Debug.hpp"
#include <ncursesw/ncurses.h> #include <ncursesw/ncurses.h>
#include <algorithm> #include <algorithm>
namespace krikkel::NCurses namespace krikkel::NCurses
{ {
using std::list; using std::list;
using std::pair;
using std::recursive_mutex; using std::recursive_mutex;
using std::scoped_lock; using std::scoped_lock;
@ -17,15 +20,23 @@ namespace krikkel::NCurses
: Window(*rootWindow), ncursesMutex(ncursesMutex) : Window(*rootWindow), ncursesMutex(ncursesMutex)
{} {}
void TilingWindowManager::addWindow(Window *window) void TilingWindowManager::addWindow(Window *window, windowDimension size)
{ {
stack.push_back(window); WindowStackElement stackElement(window, size);
visibleStack.push_back(window); stack.push_back(stackElement);
visibleStack.push_back(stackElement);
} }
void TilingWindowManager::hideWindow(Window *window) void TilingWindowManager::hideWindow(Window *window)
{ {
visibleStack.remove(window); if(window->hidden)
return;
visibleStack.remove_if(
[window](WindowStackElement element)
{
return element.first == window;
});
window->hidden = true; window->hidden = true;
} }
@ -34,18 +45,18 @@ namespace krikkel::NCurses
if(!window->hidden) if(!window->hidden)
return; return;
list<Window *>::iterator currentVisibleWindow = visibleStack.begin(); list<WindowStackElement>::iterator currentVisibleWindowElement = visibleStack.begin();
for(Window *currentWindow : stack) for(WindowStackElement currentWindowElement : stack)
{ {
if(currentWindow == window) if(currentWindowElement.first == window)
{ {
visibleStack.insert(currentVisibleWindow, window); visibleStack.insert(currentVisibleWindowElement, currentWindowElement);
window->hidden = false; window->hidden = false;
break; break;
} }
if(currentWindow != (*currentVisibleWindow)) if(currentWindowElement != (*currentVisibleWindowElement))
continue; continue;
++currentVisibleWindow; ++currentVisibleWindowElement;
} }
} }
@ -62,21 +73,73 @@ namespace krikkel::NCurses
int result = Window::refresh(); int result = Window::refresh();
for(Window *window : visibleStack) for(WindowStackElement stackElement : visibleStack)
// @todo there are return values; compound them? // @todo there are return values; compound them?
window->refresh(); stackElement.first->refresh();
return result; return result;
} }
SingleUserInput TilingWindowManager::readSingleUserInput() void TilingWindowManager::updateLayout()
{ {
return Window::readSingleUserInput(); scoped_lock lock(*ncursesMutex);
size_t stackSize = visibleStack.size();
if(stackSize == 0)
{
clear();
return;
} }
void TilingWindowManager::resizeWindow(Window *window, uint16_t height, uint16_t width, uint16_t y, uint16_t x) int unitSize = getRelativeUnitSizeForVisibleWindows(getAvailableSpace()) - 1;
windowPosition position = 0;
auto lastButNotLeast = prev(visibleStack.end());
for(auto stackElementIterator = visibleStack.begin()
; stackElementIterator != lastButNotLeast
; ++stackElementIterator)
{ {
window->resize(height, width); WindowStackElement stackElement = *stackElementIterator;
window->mvwin(y, x); Window *window = stackElement.first;
if(!window->isHidden())
{
int windowShift;
int windowSize = stackElement.second;
if(windowSize < 0)
windowShift = unitSize * (-windowSize);
else
windowShift = windowSize;
resizeAndMoveWindow(window, windowShift, position);
position += windowShift;
__debug_log("drawing line at " + std::to_string(position));
moveCursor(position++);
windowBorder();
}
}
resizeAndMoveWindow((*lastButNotLeast).first, getAvailableSpace() - position, position);
}
TilingWindowManager::windowDimension TilingWindowManager::getRelativeUnitSizeForVisibleWindows(windowDimension spaceAvailable)
{
uint16_t numberOfRelativeUnits = 0;
windowDimension absoluteSum = 0;
list<WindowStackElement>::iterator windowIterator = stack.begin();
for(WindowStackElement stackElement : visibleStack)
{
windowDimension size = stackElement.second;
if(size < 0)
numberOfRelativeUnits -= size;
else
absoluteSum += size;
}
windowDimension relativeUnitSize = (spaceAvailable - absoluteSum) / numberOfRelativeUnits;
windowDimension remainder = (spaceAvailable - absoluteSum - relativeUnitSize * numberOfRelativeUnits);
if(remainder > 0 && remainder * 2 < numberOfRelativeUnits)
++relativeUnitSize;
return relativeUnitSize;
} }
} }

View file

@ -2,6 +2,7 @@
* @author Christian Burger (christian@krikkel.de) * @author Christian Burger (christian@krikkel.de)
*/ */
#include "Debug.hpp"
#include <kNCurses/VerticalTilingWindowManager.hpp> #include <kNCurses/VerticalTilingWindowManager.hpp>
#include <kNCurses/Window.hpp> #include <kNCurses/Window.hpp>
#include <ncursesw/ncurses.h> #include <ncursesw/ncurses.h>
@ -9,45 +10,32 @@
namespace krikkel::NCurses namespace krikkel::NCurses
{ {
using std::list;
using std::recursive_mutex; using std::recursive_mutex;
using std::scoped_lock;
VerticalTilingWindowManager::VerticalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex) VerticalTilingWindowManager::VerticalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex)
: TilingWindowManager(rootWindow, ncursesMutex) : TilingWindowManager(rootWindow, ncursesMutex)
{} {}
void VerticalTilingWindowManager::updateLayout() void VerticalTilingWindowManager::resizeAndMoveWindow(Window *window, windowDimension dimension, windowPosition position)
{ {
scoped_lock lock(*ncursesMutex); __debug_log("(" + std::to_string(0 + begx()) + ", " + std::to_string(position + begy()) + ", " + std::to_string(width()) + ", " + std::to_string(dimension) + ")");
window->resize(dimension, width());
size_t stackSize = visibleStack.size(); window->mvwin(position + begy(), 0 + begx());
if(stackSize == 0)
{
clear();
return;
} }
int availableWidth = width(); TilingWindowManager::windowDimension VerticalTilingWindowManager::getAvailableSpace()
int availableHeight = height();
int windowHeight = availableHeight / stackSize - 1;
if((windowHeight + 1) * stackSize < availableHeight)
++windowHeight;
uint16_t y = 0;
list<Window *>::iterator it = visibleStack.begin();
for(size_t index = 0
; index < visibleStack.size() - 1
; ++index)
{ {
resizeWindow(*it++, windowHeight, availableWidth, y, 0); return height();
y += windowHeight; }
void VerticalTilingWindowManager::windowBorder()
{ {
move(y++, 0); hline(width());
hline(availableWidth);
} }
void VerticalTilingWindowManager::moveCursor(TilingWindowManager::windowPosition position)
{
move(position, 0);
} }
resizeWindow(*it, availableHeight - y, availableWidth, y, 0);
}
} }

View file

@ -17,7 +17,13 @@ namespace krikkel::NCurses
{ {
public: public:
HorizontalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex); HorizontalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex);
void updateLayout() override;
void resizeAndMoveWindow(Window *window
, windowDimension dimension
, windowPosition position) override;
windowDimension getAvailableSpace() override;
void windowBorder() override;
void moveCursor(windowPosition position) override;
}; };
} }

View file

@ -1,5 +1,17 @@
/** /**
* @brief Window manager * @brief Window manager for tiling (template pattern, abstract parent)
*
* A window manager contains windows of a given size, lays them out and can hide
* or show them. There is no concrete window manager for tiling horizontally and
* vertically at the same time. Instead nesting the window managers is adviced,
* since the window manager classes are derived from windows in the end.
*
* This is an abstract class used by the concrete classes
* `VerticalTilingWindowManager` and `HorizontalTilingWindowManager`. Both
* specify the virtual and protected, declared but undefined methods used by the
* public interface to facilitate the function depending on the orientation
* (horizontally or vertically).
*
* @author Christian Burger (christian@krikkel.de) * @author Christian Burger (christian@krikkel.de)
*/ */
@ -9,6 +21,7 @@
#include "Window.hpp" #include "Window.hpp"
#include <list> #include <list>
#include <mutex> #include <mutex>
#include <utility>
namespace krikkel::NCurses namespace krikkel::NCurses
{ {
@ -17,21 +30,31 @@ namespace krikkel::NCurses
class TilingWindowManager : public Window class TilingWindowManager : public Window
{ {
public: public:
/// @todo figure out more appropiate names …
typedef int16_t windowDimension;
typedef uint16_t windowPosition;
typedef std::pair<Window *, windowDimension> WindowStackElement;
TilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex); TilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex);
void addWindow(Window *window); void addWindow(Window *window, windowDimension size = -1);
int resize(int rows, int cols) override; int resize(int rows, int cols) override;
int refresh() override; int refresh() override;
virtual void updateLayout() = 0; void updateLayout();
void hideWindow(Window *window); void hideWindow(Window *window);
void showWindow(Window *window); void showWindow(Window *window);
SingleUserInput readSingleUserInput();
protected: protected:
std::recursive_mutex *ncursesMutex; std::recursive_mutex *ncursesMutex;
std::list<Window *> stack, visibleStack; std::list<WindowStackElement> stack, visibleStack;
void resizeWindow(Window *window, uint16_t height, uint16_t width, uint16_t y, uint16_t x); windowDimension getRelativeUnitSizeForVisibleWindows(windowDimension spaceAvailable);
virtual void resizeAndMoveWindow(Window *window
, windowDimension dimension
, windowPosition position) = 0;
virtual void windowBorder() = 0;
virtual windowDimension getAvailableSpace() = 0;
virtual void moveCursor(windowPosition position) = 0;
}; };
} }

View file

@ -17,7 +17,13 @@ namespace krikkel::NCurses
{ {
public: public:
VerticalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex); VerticalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex);
void updateLayout() override;
void resizeAndMoveWindow(Window *window
, windowDimension dimension
, windowPosition position) override;
windowDimension getAvailableSpace() override;
void windowBorder() override;
void moveCursor(windowPosition position) override;
}; };
} }