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);
topWindowManager
= new HorizontalTilingWindowManager(new Window(), &ncursesMutex);
bottomWindowManager
= new HorizontalTilingWindowManager(new Window(), &ncursesMutex);
windowManager->addWindow(topWindowManager);
ptyWindow = new PtyWindow(&ncursesMutex, 0, 0, 0, 0);
windowManager->addWindow(ptyWindow);
dummyWindowBottom = new Window(windowManager);
windowManager->addWindow(bottomWindowManager);
windowManager->updateLayout();
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->addstr("top right window");
topWindowManager->updateLayout();
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()
@ -110,10 +123,10 @@ namespace krikkel::NCurses
windowManager->refresh();
break;
case KEY_F(3):
if(dummyWindowBottom->isHidden())
windowManager->showWindow(dummyWindowBottom);
if(bottomWindowManager->isHidden())
windowManager->showWindow(bottomWindowManager);
else
windowManager->hideWindow(dummyWindowBottom);
windowManager->hideWindow(bottomWindowManager);
windowManager->updateLayout();
windowManager->refresh();
break;

View file

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

View file

@ -2,6 +2,7 @@
* @author Christian Burger (christian@krikkel.de)
*/
#include "Debug.hpp"
#include <kNCurses/HorizontalTilingWindowManager.hpp>
#include <kNCurses/Window.hpp>
#include <ncursesw/ncurses.h>
@ -9,45 +10,31 @@
namespace krikkel::NCurses
{
using std::list;
using std::recursive_mutex;
using std::scoped_lock;
HorizontalTilingWindowManager::HorizontalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *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);
window->mvwin(0 + begy(), position + begx());
}
size_t stackSize = visibleStack.size();
if(stackSize == 0)
{
clear();
return;
}
TilingWindowManager::windowDimension HorizontalTilingWindowManager::getAvailableSpace()
{
return width();
}
int availableWidth = width();
int availableHeight = height();
int windowWidth = availableWidth / stackSize - 1;
void HorizontalTilingWindowManager::windowBorder()
{
vline(height());
}
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);
x += windowWidth;
{
move(0, x++);
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/Window.hpp>
#include "Debug.hpp"
#include <ncursesw/ncurses.h>
#include <algorithm>
namespace krikkel::NCurses
{
using std::list;
using std::pair;
using std::recursive_mutex;
using std::scoped_lock;
@ -17,15 +20,23 @@ namespace krikkel::NCurses
: Window(*rootWindow), ncursesMutex(ncursesMutex)
{}
void TilingWindowManager::addWindow(Window *window)
void TilingWindowManager::addWindow(Window *window, windowDimension size)
{
stack.push_back(window);
visibleStack.push_back(window);
WindowStackElement stackElement(window, size);
stack.push_back(stackElement);
visibleStack.push_back(stackElement);
}
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;
}
@ -34,18 +45,18 @@ namespace krikkel::NCurses
if(!window->hidden)
return;
list<Window *>::iterator currentVisibleWindow = visibleStack.begin();
for(Window *currentWindow : stack)
list<WindowStackElement>::iterator currentVisibleWindowElement = visibleStack.begin();
for(WindowStackElement currentWindowElement : stack)
{
if(currentWindow == window)
if(currentWindowElement.first == window)
{
visibleStack.insert(currentVisibleWindow, window);
visibleStack.insert(currentVisibleWindowElement, currentWindowElement);
window->hidden = false;
break;
}
if(currentWindow != (*currentVisibleWindow))
if(currentWindowElement != (*currentVisibleWindowElement))
continue;
++currentVisibleWindow;
++currentVisibleWindowElement;
}
}
@ -62,21 +73,73 @@ namespace krikkel::NCurses
int result = Window::refresh();
for(Window *window : visibleStack)
for(WindowStackElement stackElement : visibleStack)
// @todo there are return values; compound them?
window->refresh();
stackElement.first->refresh();
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;
}
int unitSize = getRelativeUnitSizeForVisibleWindows(getAvailableSpace()) - 1;
windowPosition position = 0;
auto lastButNotLeast = prev(visibleStack.end());
for(auto stackElementIterator = visibleStack.begin()
; stackElementIterator != lastButNotLeast
; ++stackElementIterator)
{
WindowStackElement stackElement = *stackElementIterator;
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);
}
void TilingWindowManager::resizeWindow(Window *window, uint16_t height, uint16_t width, uint16_t y, uint16_t x)
TilingWindowManager::windowDimension TilingWindowManager::getRelativeUnitSizeForVisibleWindows(windowDimension spaceAvailable)
{
window->resize(height, width);
window->mvwin(y, x);
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)
*/
#include "Debug.hpp"
#include <kNCurses/VerticalTilingWindowManager.hpp>
#include <kNCurses/Window.hpp>
#include <ncursesw/ncurses.h>
@ -9,45 +10,32 @@
namespace krikkel::NCurses
{
using std::list;
using std::recursive_mutex;
using std::scoped_lock;
VerticalTilingWindowManager::VerticalTilingWindowManager(NCursesWindow *rootWindow, std::recursive_mutex *ncursesMutex)
: TilingWindowManager(rootWindow, ncursesMutex)
{}
void VerticalTilingWindowManager::updateLayout()
void VerticalTilingWindowManager::resizeAndMoveWindow(Window *window, windowDimension dimension, windowPosition position)
{
scoped_lock lock(*ncursesMutex);
size_t stackSize = visibleStack.size();
if(stackSize == 0)
{
clear();
return;
}
int availableWidth = width();
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);
y += windowHeight;
{
move(y++, 0);
hline(availableWidth);
}
}
resizeWindow(*it, availableHeight - y, availableWidth, y, 0);
__debug_log("(" + std::to_string(0 + begx()) + ", " + std::to_string(position + begy()) + ", " + std::to_string(width()) + ", " + std::to_string(dimension) + ")");
window->resize(dimension, width());
window->mvwin(position + begy(), 0 + begx());
}
TilingWindowManager::windowDimension VerticalTilingWindowManager::getAvailableSpace()
{
return height();
}
void VerticalTilingWindowManager::windowBorder()
{
hline(width());
}
void VerticalTilingWindowManager::moveCursor(TilingWindowManager::windowPosition position)
{
move(position, 0);
}
}

View file

@ -17,7 +17,13 @@ namespace krikkel::NCurses
{
public:
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)
*/
@ -9,6 +21,7 @@
#include "Window.hpp"
#include <list>
#include <mutex>
#include <utility>
namespace krikkel::NCurses
{
@ -17,21 +30,31 @@ namespace krikkel::NCurses
class TilingWindowManager : public Window
{
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);
void addWindow(Window *window);
void addWindow(Window *window, windowDimension size = -1);
int resize(int rows, int cols) override;
int refresh() override;
virtual void updateLayout() = 0;
void updateLayout();
void hideWindow(Window *window);
void showWindow(Window *window);
SingleUserInput readSingleUserInput();
protected:
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:
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;
};
}