mirror of
https://github.com/nicbarker/clay.git
synced 2026-02-06 12:48:49 +00:00
feat(ncurses): event-driven scrolling & font styling
- Update `renderers/ncurses/clay_renderer_ncurses.c`: - Export `CLAY_NCURSES_KEY_SCROLL_UP` and `CLAY_NCURSES_KEY_SCROLL_DOWN` key codes. - Modify `Clay_Ncurses_ProcessInput` to map mouse wheel events (`BUTTON4`, `BUTTON5`) to these key codes. - Update `Clay_Ncurses_OnClick` to trigger on `CLAY_POINTER_DATA_PRESSED_THIS_FRAME` for immediate feedback. - Update `examples/ncurses-example/main.c`: - Handle `CLAY_NCURSES_KEY_SCROLL_UP/DOWN` in `App_ProcessInput` to drive `_appState.scrollDelta`. - Simplify `HandleHelpToggleClick` to toggle visibility directly. - Apply bold and underline font styles to sidebar items. - Convert input processing to a `while` loop to process all pending events per frame.
This commit is contained in:
parent
c700104760
commit
e89f3d15e9
3 changed files with 68 additions and 19 deletions
|
|
@ -77,9 +77,7 @@ static AppState _appState = {
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
void HandleHelpToggleClick(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData) {
|
void HandleHelpToggleClick(Clay_ElementId elementId, Clay_PointerData pointerInfo, void *userData) {
|
||||||
if (pointerInfo.state == CLAY_POINTER_DATA_RELEASED_THIS_FRAME) {
|
_appState.isHelpModalVisible = !_appState.isHelpModalVisible;
|
||||||
_appState.isHelpModalVisible = !_appState.isHelpModalVisible;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -94,8 +92,8 @@ void HandleHelpToggleClick(Clay_ElementId elementId, Clay_PointerData pointerInf
|
||||||
void App_ProcessInput() {
|
void App_ProcessInput() {
|
||||||
_appState.scrollDelta = 0.0f;
|
_appState.scrollDelta = 0.0f;
|
||||||
|
|
||||||
int key = Clay_Ncurses_ProcessInput(stdscr);
|
int key;
|
||||||
if (key != ERR) {
|
while ((key = Clay_Ncurses_ProcessInput(stdscr)) != ERR) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'q':
|
case 'q':
|
||||||
case 'Q':
|
case 'Q':
|
||||||
|
|
@ -111,9 +109,11 @@ void App_ProcessInput() {
|
||||||
_appState.isHelpModalVisible = !_appState.isHelpModalVisible;
|
_appState.isHelpModalVisible = !_appState.isHelpModalVisible;
|
||||||
break;
|
break;
|
||||||
case KEY_UP:
|
case KEY_UP:
|
||||||
|
case CLAY_NCURSES_KEY_SCROLL_UP:
|
||||||
_appState.scrollDelta += DEFAULT_SCROLL_SENSITIVITY;
|
_appState.scrollDelta += DEFAULT_SCROLL_SENSITIVITY;
|
||||||
break;
|
break;
|
||||||
case KEY_DOWN:
|
case KEY_DOWN:
|
||||||
|
case CLAY_NCURSES_KEY_SCROLL_DOWN:
|
||||||
_appState.scrollDelta -= DEFAULT_SCROLL_SENSITIVITY;
|
_appState.scrollDelta -= DEFAULT_SCROLL_SENSITIVITY;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +237,7 @@ void UI_Sidebar() {
|
||||||
.cornerRadius = { .topLeft = 1 },
|
.cornerRadius = { .topLeft = 1 },
|
||||||
.border = { .color = COLOR_ACCENT_RED, .width = {2, 2, 2, 2} }
|
.border = { .color = COLOR_ACCENT_RED, .width = {2, 2, 2, 2} }
|
||||||
}) {
|
}) {
|
||||||
CLAY_TEXT(CLAY_STRING(" > TL Round"), CLAY_TEXT_CONFIG({ .textColor = COLOR_ACCENT_RED }));
|
CLAY_TEXT(CLAY_STRING(" > TL BOLD"), CLAY_TEXT_CONFIG({ .textColor = COLOR_ACCENT_RED, .fontId = CLAY_NCURSES_FONT_BOLD }));
|
||||||
}
|
}
|
||||||
|
|
||||||
CLAY(CLAY_ID("SidebarItemMixed2"), {
|
CLAY(CLAY_ID("SidebarItemMixed2"), {
|
||||||
|
|
@ -246,7 +246,7 @@ void UI_Sidebar() {
|
||||||
.cornerRadius = { .topLeft = 1, .bottomRight = 1 },
|
.cornerRadius = { .topLeft = 1, .bottomRight = 1 },
|
||||||
.border = { .color = {100, 255, 100, 255}, .width = {2, 2, 2, 2} }
|
.border = { .color = {100, 255, 100, 255}, .width = {2, 2, 2, 2} }
|
||||||
}) {
|
}) {
|
||||||
CLAY_TEXT(CLAY_STRING(" > Diagonal"), CLAY_TEXT_CONFIG({ .textColor = {100, 255, 100, 255} }));
|
CLAY_TEXT(CLAY_STRING(" > Diag Under"), CLAY_TEXT_CONFIG({ .textColor = {100, 255, 100, 255}, .fontId = CLAY_NCURSES_FONT_UNDERLINE }));
|
||||||
}
|
}
|
||||||
|
|
||||||
CLAY(CLAY_ID("SidebarItemMixed3"), {
|
CLAY(CLAY_ID("SidebarItemMixed3"), {
|
||||||
|
|
@ -255,7 +255,7 @@ void UI_Sidebar() {
|
||||||
.cornerRadius = { .topLeft = 1, .topRight = 1 },
|
.cornerRadius = { .topLeft = 1, .topRight = 1 },
|
||||||
.border = { .color = COLOR_ACCENT_BLUE, .width = {2, 2, 2, 2} }
|
.border = { .color = COLOR_ACCENT_BLUE, .width = {2, 2, 2, 2} }
|
||||||
}) {
|
}) {
|
||||||
CLAY_TEXT(CLAY_STRING(" > Top Round"), CLAY_TEXT_CONFIG({ .textColor = COLOR_ACCENT_BLUE }));
|
CLAY_TEXT(CLAY_STRING(" > Top Bold Und"), CLAY_TEXT_CONFIG({ .textColor = COLOR_ACCENT_BLUE, .fontId = CLAY_NCURSES_FONT_BOLD | CLAY_NCURSES_FONT_UNDERLINE }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,10 +75,21 @@ while (!shouldQuit) {
|
||||||
|
|
||||||
The renderer provides helper functions to easy integration of mouse interactions:
|
The renderer provides helper functions to easy integration of mouse interactions:
|
||||||
|
|
||||||
- **`Clay_Ncurses_ProcessInput(WINDOW *window)`**: Call this instead of `getch` or `wgetch`. It handles mouse events, updates the internal Clay pointer state, and returns the key code for your application to handle (e.g., keyboard shortcuts).
|
- **`Clay_Ncurses_ProcessInput(WINDOW *window)`**: Call this instead of `getch` or `wgetch`. It handles mouse events (including scroll wheel mapping to `Clay_UpdateScrollContainers`), updates the internal Clay pointer state, and returns the key code for your application to handle.
|
||||||
- **`Clay_Ncurses_OnClick(void (*userData)(...), void *userData)`**: A helper to attach a click listener to the current element. It uses `Clay_OnHover` internally. Your callback function should check if `pointerInfo.state == CLAY_POINTER_DATA_RELEASED_THIS_FRAME` to detect a valid click.
|
- **`Clay_Ncurses_OnClick(void (*userData)(...), void *userData)`**: A helper to attach a click listener to the current element. It uses `Clay_OnHover` internally. Your callback function should check if `pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME` for instant click feedback.
|
||||||
|
|
||||||
### 5. Cleanup
|
### 5. Font Styling
|
||||||
|
|
||||||
|
You can apply **Bold** and **Underline** styles using the `fontId` configuration in `CLAY_TEXT`.
|
||||||
|
Use the provided macros:
|
||||||
|
|
||||||
|
```c
|
||||||
|
CLAY_TEXT(CLAY_STRING("Bold Text"), CLAY_TEXT_CONFIG({ .fontId = CLAY_NCURSES_FONT_BOLD }));
|
||||||
|
CLAY_TEXT(CLAY_STRING("Underline"), CLAY_TEXT_CONFIG({ .fontId = CLAY_NCURSES_FONT_UNDERLINE }));
|
||||||
|
CLAY_TEXT(CLAY_STRING("Both"), CLAY_TEXT_CONFIG({ .fontId = CLAY_NCURSES_FONT_BOLD | CLAY_NCURSES_FONT_UNDERLINE }));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Cleanup
|
||||||
|
|
||||||
Restore the terminal to its normal state before exiting.
|
Restore the terminal to its normal state before exiting.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@
|
||||||
#include <wchar.h>
|
#include <wchar.h>
|
||||||
#include "../../clay.h"
|
#include "../../clay.h"
|
||||||
|
|
||||||
|
#define CLAY_NCURSES_FONT_BOLD 1
|
||||||
|
#define CLAY_NCURSES_FONT_UNDERLINE 2
|
||||||
|
|
||||||
|
// Custom Key Codes for Mouse Scrolling
|
||||||
|
#define CLAY_NCURSES_KEY_SCROLL_UP 123456
|
||||||
|
#define CLAY_NCURSES_KEY_SCROLL_DOWN 123457
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
// -- Internal State & Constants
|
// -- Internal State & Constants
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -51,6 +58,10 @@ static int _screenHeight = 0;
|
||||||
/** @brief Flag indicating if the ncurses subsystem has been successfully initialized. */
|
/** @brief Flag indicating if the ncurses subsystem has been successfully initialized. */
|
||||||
static bool _isInitialized = false;
|
static bool _isInitialized = false;
|
||||||
|
|
||||||
|
// Input State
|
||||||
|
static bool _pointerReleasedThisFrame = false;
|
||||||
|
static bool _pointerPressedThisFrame = false;
|
||||||
|
|
||||||
// Scissor / Clipping State
|
// Scissor / Clipping State
|
||||||
|
|
||||||
/** @brief Maximum depth of the scissor/clipping stack. */
|
/** @brief Maximum depth of the scissor/clipping stack. */
|
||||||
|
|
@ -234,6 +245,9 @@ static void Clay_Ncurses_RenderText(Clay_RenderCommand *command) {
|
||||||
int pair = Clay_Ncurses_GetColorPair(fg, bg);
|
int pair = Clay_Ncurses_GetColorPair(fg, bg);
|
||||||
|
|
||||||
attron(COLOR_PAIR(pair));
|
attron(COLOR_PAIR(pair));
|
||||||
|
attron(COLOR_PAIR(pair));
|
||||||
|
if (command->renderData.text.fontId & CLAY_NCURSES_FONT_BOLD) attron(A_BOLD);
|
||||||
|
if (command->renderData.text.fontId & CLAY_NCURSES_FONT_UNDERLINE) attron(A_UNDERLINE);
|
||||||
|
|
||||||
// Complex multibyte string handling
|
// Complex multibyte string handling
|
||||||
// We render to a temporary buffer first to handle wide characters
|
// We render to a temporary buffer first to handle wide characters
|
||||||
|
|
@ -277,6 +291,8 @@ static void Clay_Ncurses_RenderText(Clay_RenderCommand *command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
free(wbuf);
|
free(wbuf);
|
||||||
|
if (command->renderData.text.fontId & CLAY_NCURSES_FONT_BOLD) attroff(A_BOLD);
|
||||||
|
if (command->renderData.text.fontId & CLAY_NCURSES_FONT_UNDERLINE) attroff(A_UNDERLINE);
|
||||||
attroff(COLOR_PAIR(pair));
|
attroff(COLOR_PAIR(pair));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -428,7 +444,12 @@ void Clay_Ncurses_Initialize() {
|
||||||
// We only ask for PRESS and RELEASE events.
|
// We only ask for PRESS and RELEASE events.
|
||||||
// If we ask for CLICK events, ncurses waits to see if a release happens quickly,
|
// If we ask for CLICK events, ncurses waits to see if a release happens quickly,
|
||||||
// which delays the report of the PRESS event or swallows it, causing Clay to miss the "Down" state.
|
// which delays the report of the PRESS event or swallows it, causing Clay to miss the "Down" state.
|
||||||
mousemask(BUTTON1_PRESSED | BUTTON1_RELEASED | REPORT_MOUSE_POSITION, NULL);
|
// We only ask for PRESS and RELEASE events.
|
||||||
|
// If we ask for CLICK events, ncurses waits to see if a release happens quickly,
|
||||||
|
// which delays the report of the PRESS event or swallows it, causing Clay to miss the "Down" state.
|
||||||
|
mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL);
|
||||||
|
// Disable strict click resolution to avoid input lag.
|
||||||
|
mouseinterval(0);
|
||||||
|
|
||||||
start_color();
|
start_color();
|
||||||
use_default_colors();
|
use_default_colors();
|
||||||
|
|
@ -496,6 +517,9 @@ Clay_Dimensions Clay_Ncurses_MeasureText(Clay_StringSlice text, Clay_TextElement
|
||||||
void Clay_Ncurses_Render(Clay_RenderCommandArray renderCommands) {
|
void Clay_Ncurses_Render(Clay_RenderCommandArray renderCommands) {
|
||||||
if (!_isInitialized) return;
|
if (!_isInitialized) return;
|
||||||
|
|
||||||
|
// Reset input state for next frame
|
||||||
|
_pointerPressedThisFrame = false;
|
||||||
|
|
||||||
// Update screen dimensions if terminal successfully resized
|
// Update screen dimensions if terminal successfully resized
|
||||||
int newW, newH;
|
int newW, newH;
|
||||||
getmaxyx(stdscr, newH, newW);
|
getmaxyx(stdscr, newH, newW);
|
||||||
|
|
@ -624,8 +648,6 @@ static int Clay_Ncurses_MeasureStringWidth(Clay_StringSlice text) {
|
||||||
* @param window The Ncurses window to read input from (e.g. stdscr).
|
* @param window The Ncurses window to read input from (e.g. stdscr).
|
||||||
* @return The key code pressed, or ERR if no input.
|
* @return The key code pressed, or ERR if no input.
|
||||||
*/
|
*/
|
||||||
static bool _pointerReleasedThisFrame = false;
|
|
||||||
|
|
||||||
int Clay_Ncurses_ProcessInput(WINDOW *window) {
|
int Clay_Ncurses_ProcessInput(WINDOW *window) {
|
||||||
int key = wgetch(window);
|
int key = wgetch(window);
|
||||||
_pointerReleasedThisFrame = false;
|
_pointerReleasedThisFrame = false;
|
||||||
|
|
@ -643,16 +665,31 @@ int Clay_Ncurses_ProcessInput(WINDOW *window) {
|
||||||
// Persistent state to handle drag/move events where button state might be absent in the event mask
|
// Persistent state to handle drag/move events where button state might be absent in the event mask
|
||||||
static bool _isMouseDown = false;
|
static bool _isMouseDown = false;
|
||||||
|
|
||||||
|
// Update Clay State FIRST so scroll/interaction logic knows where the mouse is
|
||||||
|
Clay_SetPointerState(mousePos, _isMouseDown);
|
||||||
|
|
||||||
if (event.bstate & (BUTTON1_PRESSED | BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED)) {
|
if (event.bstate & (BUTTON1_PRESSED | BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED)) {
|
||||||
_isMouseDown = true;
|
_isMouseDown = true;
|
||||||
|
if (event.bstate & BUTTON1_PRESSED) {
|
||||||
|
_pointerPressedThisFrame = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.bstate & BUTTON1_RELEASED) {
|
if (event.bstate & BUTTON1_RELEASED) {
|
||||||
_isMouseDown = false;
|
_isMouseDown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Clay State
|
// Handle Scroll Wheel
|
||||||
Clay_SetPointerState(mousePos, _isMouseDown);
|
#ifdef BUTTON4_PRESSED
|
||||||
|
if (event.bstate & BUTTON4_PRESSED) {
|
||||||
|
return CLAY_NCURSES_KEY_SCROLL_UP;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef BUTTON5_PRESSED
|
||||||
|
if (event.bstate & BUTTON5_PRESSED) {
|
||||||
|
return CLAY_NCURSES_KEY_SCROLL_DOWN;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -661,13 +698,14 @@ int Clay_Ncurses_ProcessInput(WINDOW *window) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Helper to attach an OnClick listener to the current element.
|
* @brief Helper to attach an OnClick listener to the current element.
|
||||||
* Registers a hover callback. The user's function must check `pointerData.state == CLAY_POINTER_DATA_RELEASED_THIS_FRAME`.
|
* Registers a hover callback. The user's function must check `pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME`.
|
||||||
*
|
*
|
||||||
* @param onClickFunc Function pointer to call.
|
* @param onClickFunc Function pointer to call.
|
||||||
* @param userData User data passed to the callback.
|
* @param userData User data passed to the callback.
|
||||||
*/
|
*/
|
||||||
void Clay_Ncurses_OnClick(void (*onClickFunc)(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData), void *userData) {
|
void Clay_Ncurses_OnClick(void (*onClickFunc)(Clay_ElementId elementId, Clay_PointerData pointerData, void *userData), void *userData) {
|
||||||
if (onClickFunc) {
|
if (onClickFunc && Clay_Hovered() && _pointerPressedThisFrame) {
|
||||||
Clay_OnHover(onClickFunc, userData);
|
Clay_PointerData pointerData = (Clay_PointerData){ .state = CLAY_POINTER_DATA_PRESSED_THIS_FRAME };
|
||||||
|
onClickFunc((Clay_ElementId){0}, pointerData, userData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue