Skip to content

Commit

Permalink
[SUTK] Vertical Text Scrolling and various other improvements
Browse files Browse the repository at this point in the history
- Added IGfxDriver::getObject() function to query reference to SGE::RenderObject in a unified way
- Added InputDriver::getOnScrollEvent() to implement text scrolling
- Renamed Renderable::setClipRect to setClipRectGlobalCoords and created a local variant which expects coordinates in the local space of its renderable container's parent.
- Implemented Caret Rendering and clipping inside the textbox etc; and text scrolling (up and down) using the scroll wheel.
  • Loading branch information
ravi688 committed Aug 5, 2024
1 parent f9fbf80 commit addbf03
Show file tree
Hide file tree
Showing 13 changed files with 164 additions and 12 deletions.
2 changes: 2 additions & 0 deletions sutk/include/sutk/IGfxDriver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ namespace SUTK
virtual void setTextPosition(GfxDriverObjectHandleType handle, Vec2Df position) = 0;
virtual void setTextData(GfxDriverObjectHandleType handle, const std::string& data) = 0;
virtual GfxDriverObjectHandleType getTextObject(GfxDriverObjectHandleType handle) = 0;

virtual GfxDriverObjectHandleType getObject(GfxDriverObjectHandleType handle) = 0;
// rect should be in centimeters
virtual void setObjectScissor(GfxDriverObjectHandleType handle, const Rect2Df rect) = 0;
// position should be centimeters
Expand Down
2 changes: 2 additions & 0 deletions sutk/include/sutk/IInputDriver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,13 @@ namespace SUTK
private:
com::Event<IInputDriver, Vec2Df> m_onCursorMoveEvent;
com::Event<IInputDriver, MouseButton, KeyEvent> m_onMouseButtonEvent;
com::Event<IInputDriver, Vec2Df> m_onScrollEvent;
public:
virtual ~IInputDriver() = default;

com::Event<IInputDriver, Vec2Df>& getOnCursorMoveEvent() { return m_onCursorMoveEvent; }
com::Event<IInputDriver, MouseButton, KeyEvent>& getOnMouseButtonEvent() { return m_onMouseButtonEvent; }
com::Event<IInputDriver, Vec2Df>& getOnScrollEvent() { return m_onScrollEvent; }

// Keyboard
virtual bool getKey(KeyCode keycode) noexcept = 0;
Expand Down
6 changes: 6 additions & 0 deletions sutk/include/sutk/Renderable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ namespace SUTK
// Implementation of Renderable
virtual bool isDirty() = 0;
virtual void update() = 0;

void setClipRectGlobalCoords(const Rect2Df rect) noexcept;

// sets the clip rect in the local coordinates of the parent container of its renderable container
// if parent is NULL then it does it in the local coordinates of its renderable container
void setClipRect(const Rect2Df rect) noexcept;
};

class GeometryRenderable : public GfxDriverRenderable
Expand Down
11 changes: 11 additions & 0 deletions sutk/include/sutk/SGEGfxDriver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ namespace SUTK
SGE::Mesh mesh;
SGE::Shader shader;
};

enum class ObjectType : u8
{
Text,
Mesh
};

class Geometry;
class SGEGfxDriver : public IGfxDriver
{
Expand All @@ -45,6 +52,7 @@ namespace SUTK
std::unordered_map<id_generator_id_type_t, SGEMeshData> m_meshMappings;
std::unordered_map<GfxDriverObjectHandleType, SGEBitmapTextStringData> m_bitmapTextStringMappings;
std::unordered_map<id_generator_id_type_t, SGE::RenderObject> m_renderObjectMappings;
std::unordered_map<id_generator_id_type_t, ObjectType> m_typeTable;
struct OnResizeCallbackHandlerData
{
OnResizeCallbackHandler handler;
Expand Down Expand Up @@ -74,6 +82,8 @@ namespace SUTK
SGE::Shader compileShader(const Geometry& geometry);
// Transforms SUTK coordinates (origin at top-left, and y downwards) to SGE coordinates (origin at center, and y upwards)
vec3_t SUTKToSGECoordTransform(const Vec2Df position);
ObjectType getType(GfxDriverObjectHandleType handle);
void removeIDFromTypeTable(u32 id);

public:

Expand All @@ -96,6 +106,7 @@ namespace SUTK
virtual void setTextData(GfxDriverObjectHandleType handle, const std::string& data) override;

virtual GfxDriverObjectHandleType getTextObject(GfxDriverObjectHandleType handle) override;
virtual GfxDriverObjectHandleType getObject(GfxDriverObjectHandleType handle) override;
virtual void setObjectScissor(GfxDriverObjectHandleType handle, const Rect2Df rect) override;
virtual void setObjectPosition(GfxDriverObjectHandleType handle, const Vec2Df position) override;

Expand Down
16 changes: 13 additions & 3 deletions sutk/include/sutk/Text.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ namespace SUTK
// Implementation of Renderable::isDirty() and Renderable::update()
virtual bool isDirty() override;
virtual void update() override;

void setClipRect(const Rect2Df rect) noexcept;

void setData(const std::string& data) noexcept;
void append(const std::string& data) noexcept;
void insert(LineCountType col, const std::string& data) noexcept;
void setPosition(Vec2Df pos) noexcept;
Vec2Df getPosition() const noexcept { return m_pos; }
void addPosition(Vec2Df pos) noexcept;
void subPosition(Vec2Df pos) noexcept;
void clear() noexcept;
Expand Down Expand Up @@ -158,20 +158,27 @@ namespace SUTK
HorizontalAlignment m_horizontalAlignment;
VerticalAlignment m_verticalAlignment;

// m_scrollDelta.y represents scroll delta in y direction (vertical)
// m_scrollDelta.x represents scroll delta in x direction (horizontal)
Vec2Df m_scrollDelta;

// this can only be called by SUTK::UIDriver
Text(UIDriver& driver, RenderableContainer* container) noexcept;

friend class UIDriver;

Vec2Df getLocalPositionFromCursorPosition(const CursorPosition<LineCountType>& cursor) noexcept;
void recalculateClipRect() noexcept;

// Overrides of Renderable::onContainerResize
virtual void onGlobalCoordDirty() noexcept override;
virtual void onContainerResize(Rect2Df rect, bool isPositionChanged, bool isSizeChanged) noexcept override;

std::pair<s32, s32> getUnclippedLineRange() noexcept;
public:

Vec2Df getLocalPositionFromCursorPosition(const CursorPosition<LineCountType>& cursor) noexcept;
CursorPosition<LineCountType> getCursorPosFromCoords(Vec2Df coords) noexcept;

enum class Flags
{
Before,
Expand All @@ -192,5 +199,8 @@ namespace SUTK
void remove(const CursorPosition<LineCountType>& position, LineCountType numChars) noexcept;
void set(const std::string& str) noexcept;
void enableClipping(bool isEnable = true) noexcept;
void setScrollDelta(Vec2Df delta) noexcept;
void addScrollDelta(Vec2Df delta) noexcept;
Vec2Df getScrollDelta() const noexcept { return m_scrollDelta; }
};
}
2 changes: 2 additions & 0 deletions sutk/include/sutk/UIDriver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ namespace SUTK
IInputDriver* m_inputDriver;
bool m_isDummyInputDriver;
std::vector<Renderable*> m_renderables;

friend class Renderable;
public:
UIDriver(IGfxDriver& gfxDriver, IInputDriver& inputDriver) noexcept;
UIDriver(IGfxDriver& gfxDriver) noexcept;
Expand Down
11 changes: 11 additions & 0 deletions sutk/include/sutk/defines.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ namespace SUTK
T width;
T height;

// getters
T getWidth() const noexcept { return width; }
T getHeight() const noexcept { return height; }

constexpr Rect2D() noexcept : x(0), y(0), width(0), height(0) { }
constexpr Rect2D(T _x, T _y, T _width, T _height) noexcept : x(_x), y(_y), width(_width), height(_height) { }
constexpr Rect2D(Vec2D<T> pos, Vec2D<T> size) noexcept : x(pos.x), y(pos.y), width(size.width), height(size.height) { }
Expand Down Expand Up @@ -308,6 +312,13 @@ namespace SUTK
height += value;
}

bool contains(Vec2D<T> point) noexcept
{
auto tl = getTopLeft();
auto br = getBottomRight();
return (point.x > tl.x) && (point.y > tl.y) && (point.x < br.x) && (point.y < br.y);
}

std::string toString() const noexcept
{
// NOTE: we are creating here r-value std::ostringstream object
Expand Down
8 changes: 8 additions & 0 deletions sutk/source/RenderRect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ namespace SUTK
.quad(3, 2, 4, 5) // right line
.quad(5, 4, 6, 7) // bottom line
.quad(7, 6, 1, 0);// left line

// call update for the first time as we have geometry description already and
// this geometry is supposed to be displayed in the very first frame.
update();
}

bool RenderRectOutline::isDirty()
Expand Down Expand Up @@ -124,6 +128,10 @@ namespace SUTK
.topology(Geometry::Topology::TriangleList)
.quad(0, 1, 2, 3)
.fillColor(m_color);

// call update for the first time as we have geometry description already and
// this geometry is supposed to be displayed in the very first frame.
update();
}

bool RenderRectFill::isDirty()
Expand Down
18 changes: 18 additions & 0 deletions sutk/source/Renderable.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
#include <sutk/Renderable.hpp>
#include <sutk/RenderableContainer.hpp> // for SUTK::RenderableContainer::setRenderable()
#include <sutk/IGfxDriver.hpp>

namespace SUTK
{
Renderable::Renderable(UIDriver& driver, RenderableContainer* container) noexcept : UIDriverObject(driver), m_container(container)
{
if(container != NULL)
container->setRenderable(this);
driver.m_renderables.push_back(this);
}

void GfxDriverRenderable::setClipRectGlobalCoords(const Rect2Df rect) noexcept
{
auto handle = getGfxDriverObjectHandle();
getGfxDriver().setObjectScissor(getGfxDriver().getObject(handle), rect);
}

void GfxDriverRenderable::setClipRect(const Rect2Df rect) noexcept
{
Container* container = getContainer();
if(container->getParent() != NULL)
container = container->getParent();
// convert the top-left corner of the container, in which renderable is, to global coordinates
auto position = container->getLocalCoordsToScreenCoords(rect.getPosition());
setClipRectGlobalCoords({ position, rect.getSize() });
}
}
36 changes: 35 additions & 1 deletion sutk/source/SGEGfxDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ namespace SUTK
subText.setPointSize(24);
u32 id = id_generator_get(&m_id_generator);
m_bitmapTextStringMappings.insert({ id, { subText, bitmapText.second } });
m_typeTable.insert({ id, ObjectType::Text });
return static_cast<GfxDriverObjectHandleType>(BIT64_PACK32(id, U32_MAX));
}

Expand All @@ -202,13 +203,21 @@ namespace SUTK
return it->second.textString;
}

void SGEGfxDriver::removeIDFromTypeTable(u32 id)
{
auto it = m_typeTable.find(id);
_assert(it != m_typeTable.end());
m_typeTable.erase(it);
}

void SGEGfxDriver::destroyText(GfxDriverObjectHandleType handle)
{
auto it = getSubTextIterator(handle);
SGE::BitmapTextString subText = it->second.textString;
subText.destroy();
id_generator_return(&m_id_generator, static_cast<id_generator_id_type_t>(handle));
id_generator_return(&m_id_generator, static_cast<id_generator_id_type_t>(GET_ATTACHED_OBJECT_ID(handle)));
m_bitmapTextStringMappings.erase(it);
removeIDFromTypeTable(GET_ATTACHED_OBJECT_ID(handle));
}

vec3_t SGEGfxDriver::SUTKToSGECoordTransform(const Vec2Df position)
Expand Down Expand Up @@ -241,6 +250,28 @@ namespace SUTK
return it->second.textHandle;
}

ObjectType SGEGfxDriver::getType(GfxDriverObjectHandleType handle)
{
u32 id = GET_ATTACHED_OBJECT_ID(handle);
auto it = m_typeTable.find(id);
_assert(it != m_typeTable.end());
return it->second;
}

GfxDriverObjectHandleType SGEGfxDriver::getObject(GfxDriverObjectHandleType handle)
{
ObjectType type = getType(handle);
switch(type)
{
case ObjectType::Text:
return getTextObject(handle);
case ObjectType::Mesh:
return getGeometryObject(handle);
}
debug_log_error("Invalid ObjectType: %u", static_cast<u8>(type));
return GFX_DRIVER_OBJECT_NULL_HANDLE;
}

void SGEGfxDriver::setObjectScissor(GfxDriverObjectHandleType handle, const Rect2Df rect)
{
auto it = getRenderObjectIterator(handle);
Expand Down Expand Up @@ -285,6 +316,8 @@ namespace SUTK
id_generator_id_type_t meshID = id_generator_get(&m_id_generator);
m_meshMappings.insert({ meshID, { mesh, shader } });

m_typeTable.insert({ meshID, ObjectType::Mesh });

// add corresponding render object into the render object mappings table
id_generator_id_type_t renderObjectID = id_generator_get(&m_id_generator);
m_renderObjectMappings.insert({ renderObjectID, object });
Expand Down Expand Up @@ -394,6 +427,7 @@ namespace SUTK
it->second.mesh.destroy();
id_generator_return(&m_id_generator, static_cast<id_generator_id_type_t>(GET_ATTACHED_OBJECT_ID(geometry)));
m_meshMappings.erase(it);
removeIDFromTypeTable(GET_ATTACHED_OBJECT_ID(geometry));

// erase the corresponding render object also
auto it2 = getRenderObjectIterator(geometry);
Expand Down
9 changes: 9 additions & 0 deletions sutk/source/SGEInputDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ namespace SUTK
auto keyEventType = window.getKeyEventType();
inputDriver->getOnMouseButtonEvent().publish(SUTK::getMouseButton(mouseButtonType), SUTK::getKeyEventType(keyEventType));
}, reinterpret_cast<void*>(this));

getOnScrollEvent().setPublisher(this);
m_window.getOnScrollEvent().subscribe([](void* publisher, void* handlerData)
{
auto* inputDriver = reinterpret_cast<SGEInputDriver*>(handlerData);
auto window = inputDriver->m_window;
auto delta = window.getScrollDelta();
inputDriver->getOnScrollEvent().publish({ delta.x, delta.y });
}, reinterpret_cast<void*>(this));
}

// Keyboard
Expand Down
53 changes: 47 additions & 6 deletions sutk/source/Text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <common/assert.h> /* for _assert() */

#include <algorithm> // for std::min and std::clamp

namespace SUTK
{
template<> CursorPosition<LineCountType> CursorPosition<LineCountType>::EndOfText() { return { END_OF_TEXT, END_OF_LINE }; }
Expand All @@ -32,10 +34,6 @@ namespace SUTK
m_isDataDirty = false;
}
}
void LineText::setClipRect(const Rect2Df rect) noexcept
{
getGfxDriver().setTextScissor(getGfxDriverObjectHandle(), rect);
}
void LineText::setData(const std::string& data) noexcept
{
m_data = std::string(data);
Expand Down Expand Up @@ -117,7 +115,32 @@ namespace SUTK

Vec2Df Text::getLocalPositionFromCursorPosition(const CursorPosition<LineCountType>& cursor) noexcept
{
return { 0, m_baselineHeight * cursor.getLine() };
return { 0, m_baselineHeight * cursor.getLine() + m_scrollDelta.y };
}

std::pair<s32, s32> Text::getUnclippedLineRange() noexcept
{
auto rect = getContainer()->getRect();
auto scrollDelta = getScrollDelta();
s32 lowerBound = static_cast<s32>(-scrollDelta.y / m_baselineHeight);
s32 upperBound = static_cast<s32>((-scrollDelta.y + rect.getHeight()) / m_baselineHeight);
return {
std::clamp(lowerBound, 0, static_cast<s32>(m_lines.size()) - 1),
std::clamp(upperBound, 0, static_cast<s32>(m_lines.size()) - 1)
};
}

CursorPosition<LineCountType> Text::getCursorPosFromCoords(Vec2Df coords) noexcept
{
if(m_lines.size() == 0)
return { 0, 0 };
std::pair<s32, s32> range = getUnclippedLineRange();
auto lineNo = static_cast<s32>((coords - m_scrollDelta).y / m_baselineHeight);
lineNo = std::min(lineNo, static_cast<s32>(m_lines.size()) - 1);
lineNo = std::max(lineNo, 0);
LineCountType colNo = 0;
_assert(lineNo >= 0);
return { lineNo, colNo };
}

// this creates a new line before line pointed by current cursor
Expand Down Expand Up @@ -234,7 +257,7 @@ namespace SUTK
for(std::size_t i = 0; i < m_lines.size(); i++)
{
// set the clip rect, note that width and height should remain as that of its text container
m_lines[i]->setClipRect({ position.x, position.y, container->getRect().width, container->getRect().height });
m_lines[i]->setClipRectGlobalCoords({ position.x, position.y, container->getRect().width, container->getRect().height });
}
}

Expand All @@ -244,6 +267,24 @@ namespace SUTK
recalculateClipRect();
}

void Text::setScrollDelta(Vec2Df delta) noexcept
{
for(std::size_t i = 0; i < m_lines.size(); i++)
{
LineText* lineText = m_lines[i];
auto pos = lineText->getPosition();
pos -= m_scrollDelta;
pos += delta;
lineText->setPosition(pos);
}
m_scrollDelta = delta;
}

void Text::addScrollDelta(Vec2Df delta) noexcept
{
setScrollDelta(m_scrollDelta + delta);
}

void Text::onGlobalCoordDirty() noexcept
{
onContainerResize(getContainer()->getRect(), true, true);
Expand Down
Loading

0 comments on commit addbf03

Please sign in to comment.