17168 lines
560 KiB
C
17168 lines
560 KiB
C
// Public Domain. See "unlicense" statement at the end of this file.
|
|
|
|
// QUICK NOTES
|
|
//
|
|
// General
|
|
// - dr_gui is a low-level GUI system that works on generic objects referred to as "elements".
|
|
// - An element is the most basic unit in dr_gui. It contains basic information about it's layout and hierarchy.
|
|
// - Elements can be used as the building blocks for more complex controls such as list boxes and scrollbars.
|
|
// - The layout of elements use floats instead of integers. The rationale for this is that it makes it easier to do certain
|
|
// layout arithmetic. For example, if you want to evenly distribute 3 elements across a fixed area, the integer based
|
|
// arithmetic can cause rounding errors which cause the elements to not sit flush against the area. By using float-based
|
|
// arithmetic we can avoid that particular issue.
|
|
//
|
|
// Hierarchy
|
|
// - An element can have a parent and any number of children. If an element does not have a parent, it is referred to as the
|
|
// top-level element.
|
|
// - When an element is deleted, it's children will be deleted as well.
|
|
// - Top-level elements do not have siblings.
|
|
//
|
|
// Event Handling
|
|
// - The application must notify dr_gui of application-generated events such as key strokes and mouse movements. These are
|
|
// referred to as inbound events. An event that is generated by dr_gui are referred to as outbound events.
|
|
// - Inbound events are used to generate outbound events. For example, a mouse-move inbound event will generate an outbound
|
|
// mouse-move event, and perhaps a mouse leave/enter pair.
|
|
// - Outbound events are posted and handled immediately. A call to drgui_post_inbound_event() will not return until all of
|
|
// the outbound events it generates have been handled.
|
|
// - Inbound events are not thread safe, however an application is free to post an inbound event from any thread so long as
|
|
// it does it's own synchronization.
|
|
// - Inbound events will typically specify the relevant top-level element and let dr_gui do the relevant processing required
|
|
// to generate the appropriate outbound events. For example, the mouse-move event will be specified with respect to the top-
|
|
// level element, but dr_gui will determine the exact child element that the mouse moved on and thus should receive the
|
|
// relevant outbound mouse-move event.
|
|
// - There are some special events that are handled differently to normal events. The best example is the paint events. The
|
|
// paint event is only called from drgui_draw().
|
|
// - Key press/release events are only ever posted to the element that has the keyboard capture/focus which is set with
|
|
// drgui_capture_keyboard(). Thus, when posting an inbound key event, a top-level element is not required when posting
|
|
// those events. The relevant context is still required, however.
|
|
//
|
|
// Global Outbound Event Handling
|
|
// - At times dr_gui will need to notify the host application in order for certain functionality to work properly. For example.
|
|
// when the mouse is captured it won't work 100% correct unless the host application has a chance to capture the mouse against
|
|
// the container window. Because dr_gui has no notion of a window system it relies on the host application to handle this
|
|
// properly.
|
|
// - A global outbound event handler should be implemented for each of the following events:
|
|
// - on_dirty: Called when a region of an element is marked as dirty and needs to be redrawn. The application will want to
|
|
// invalidate the container window to trigger an operating system redraw. Set this with drgui_set_global_on_dirty().
|
|
// - on_capture_mouse: Called when the mouse is captured and gives the application the opportunity to capture the mouse against
|
|
// the container window at the operating system level. Set with drgui_set_global_on_capture_mouse().
|
|
// - on_release_mouse: Called when the mouse is released. The opposite of on_capture_mouse.
|
|
// - on_capture_keyboard: Called when an element is given the keyboard focus and gives the application the opportunity to
|
|
// apply the keyboard focus to the container window. Set with drgui_set_global_on_capture_keyboard().
|
|
// - on_release_keyboard: Called when an element loses the keyboard focus. The opposite of on_capture_keyboard.
|
|
// - on_change_cursor: Called when the current cursor needs to be changed as a result of the mouse moving over a new element.
|
|
//
|
|
// Layout
|
|
// - An element's data structure does not store it's relative position but instead stores it's absolute position. The rationale
|
|
// for this is that storing it as relative complicates absolute positioning calculations because it would need to do a recursive
|
|
// traversal of the element's ancestors.
|
|
// - Child elements can be scaled by setting an element's inner scale. The inner scale does not scale the element itself - only
|
|
// it's children.
|
|
// - When an element is drawn, everything is scaled by it's inner scale. For example, if the inner scale is 2x and a 100x100 quad
|
|
// is drawn, the quad will be scaled to 200x200. An exception to this rule is fonts, which are never scaled. This 0s because
|
|
// text is always drawn based on the size of the font.
|
|
// - Applications should only need to work on unscaled coordinates. That is, an application should never need to worry about
|
|
// manual scaling, except for fonts. When positioning and sizing child elements, they should be done based on unscaled
|
|
// coordinates.
|
|
// - Use the inner scale system for DPI awareness.
|
|
// - The inner scale is applied recursively. That is, if a top level element has it's inner scale set to 2x and one of it's
|
|
// children has an inner scale of 2x, the actual inner scale of the child element will be 4x.
|
|
//
|
|
//
|
|
// Drawing/Painting
|
|
// - Drawing is one of the more complex parts of the GUI because it can be a bit unintuitive regarding exactly when an element
|
|
// is drawn and when a drawing function is allowed to be called.
|
|
// - To draw an element, call drgui_draw(). This takes a pointer to the element to draw and the rectangle region that should
|
|
// be redrawn. Any children that fall inside the specified rectangle region will be redrawn as well. You do not want to call
|
|
// drgui_draw() on a parent element and then again on it's children because dr_gui will do that automatically.
|
|
// - drgui_draw() does not draw anything directly, but rather calls painting callback routines which is where the actual
|
|
// drawing takes place.
|
|
// - Sometimes an application will need to be told when a region of an element is dirty and needs redrawing. An example is
|
|
// event-driven, non real-time applications such as normal desktop applications. To mark an element as dirty, you call the
|
|
// drgui_dirty() function which takes the element that is dirty, and the rectangle region that needs to be redrawn. This
|
|
// does not redraw the element immediately, but instead posts an on_dirty event for the application. Marking regions as dirty
|
|
// is not strictly required, but you should prefer it for event-driven applications that require painting operations to be
|
|
// performed at specific times (such as inside Win32's WM_PAINT messages).
|
|
// - Some operations will cause a region of an element to become dirty - such as when it is resized. dr_gui will
|
|
// automatically mark the relevant regions as dirty which in turn will cause a paint message to be posted. If this is not
|
|
// required, it can be disabled with drgui_disable_auto_dirty(). You may want to disable automatic dirtying if you are
|
|
// running a real-time application like a game which would redraw the entire GUI every frame anyway and thus not require
|
|
// handling of the paint message.
|
|
// - Real-time application guidelines (games, etc.):
|
|
// - drgui_disable_auto_dirty()
|
|
// - drgui_draw(pTopLevelElement, 0, 0, viewportWidth, viewportHeight) at the end of every frame after your main loop.
|
|
//
|
|
|
|
|
|
//
|
|
// OPTIONS
|
|
//
|
|
// #define DRGUI_NO_DR_2D
|
|
// Disable dr_2d integration. Disabling dr_2d will require you to implement your own drawing callbacks.
|
|
//
|
|
// #define DRGUI_NO_TEXT_EDITING
|
|
// Disables the text box control and text engine.
|
|
|
|
|
|
//
|
|
// EXAMPLES
|
|
//
|
|
// Basic Drawing:
|
|
//
|
|
// drgui_draw(pTopLevelElement, 0, 0, drgui_get_width(pTopLevelElement), drgui_get_height(pTopLevelElement));
|
|
//
|
|
// -------------------------
|
|
//
|
|
// Event-Driven Drawing (Win32):
|
|
//
|
|
// void my_global_on_dirty_win32(drgui_element* pElement, drgui_rect relativeRect) {
|
|
// drgui_rect absoluteRect = relativeRect;
|
|
// drgui_make_rect_absolute(pElement, &absoluteRect);
|
|
//
|
|
// RECT rect;
|
|
// rect.left = absoluteRect.left;
|
|
// rect.top = absoluteRect.top;
|
|
// rect.right = absoluteRect.right;
|
|
// rect.height = absoluteRect.bottom;
|
|
// InvalidateRect((HWND)drgui_get_user_data(drgui_find_top_level_element(pElement)), &rect, FALSE);
|
|
// }
|
|
//
|
|
// ...
|
|
//
|
|
// LRESULT CALLBACK MyWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
|
// ...
|
|
// drgui_element* pTopLevelElement = (drgui_element*)GetWindowLongPtr(hWnd, 0);
|
|
// if (pTopLevelElement != NULL) {
|
|
// switch (msg) {
|
|
// ...
|
|
// case WM_PAINT:
|
|
// {
|
|
// RECT rect;
|
|
// if (GetUpdateRect(hWnd, &rect, FALSE)) {
|
|
// drgui_draw(pTopLevelElement, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
|
|
// }
|
|
//
|
|
// break;
|
|
// }
|
|
// ...
|
|
// }
|
|
// }
|
|
// ...
|
|
// }
|
|
//
|
|
|
|
|
|
|
|
#ifndef dr_gui_h
|
|
#define dr_gui_h
|
|
|
|
#ifndef DRGUI_NO_DR_2D
|
|
// If you're using easy_draw integration, set the path below to the relative location of dr_2d. By default, the
|
|
// following structure is assumed:
|
|
// <Base Directory>
|
|
// - dr_libs
|
|
// - dr_2d.h
|
|
// - dr_gui.h
|
|
#include "dr_2d.h"
|
|
#endif
|
|
|
|
#include <stdbool.h>
|
|
|
|
#ifndef DRGUI_MAX_FONT_FAMILY_LENGTH
|
|
#define DRGUI_MAX_FONT_FAMILY_LENGTH 128
|
|
#endif
|
|
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef struct drgui_context drgui_context;
|
|
typedef struct drgui_element drgui_element;
|
|
typedef struct drgui_color drgui_color;
|
|
typedef struct drgui_rect drgui_rect;
|
|
typedef struct drgui_painting_callbacks drgui_painting_callbacks;
|
|
typedef struct drgui_font drgui_font;
|
|
typedef struct drgui_image drgui_image;
|
|
typedef struct drgui_font_metrics drgui_font_metrics;
|
|
typedef struct drgui_glyph_metrics drgui_glyph_metrics;
|
|
|
|
typedef unsigned char drgui_byte;
|
|
typedef unsigned int drgui_key;
|
|
|
|
typedef void* drgui_resource;
|
|
|
|
/// Common system cursors.
|
|
typedef enum
|
|
{
|
|
drgui_cursor_none,
|
|
drgui_cursor_default,
|
|
drgui_cursor_arrow = drgui_cursor_default,
|
|
drgui_cursor_text,
|
|
drgui_cursor_cross,
|
|
drgui_cursor_size_ns, // North/South resize arrows.
|
|
drgui_cursor_size_we, // West/East resize arrows.
|
|
drgui_cursor_size_nesw, // North/East, South/West resize arrows.
|
|
drgui_cursor_size_nwse // North/West, South/East resize arrows.
|
|
} drgui_cursor_type;
|
|
|
|
/// Font weights.
|
|
typedef enum
|
|
{
|
|
drgui_font_weight_medium,
|
|
drgui_font_weight_thin,
|
|
drgui_font_weight_extra_light,
|
|
drgui_font_weight_light,
|
|
drgui_font_weight_semi_light,
|
|
drgui_font_weight_book,
|
|
drgui_font_weight_semi_bold,
|
|
drgui_font_weight_bold,
|
|
drgui_font_weight_extra_bold,
|
|
drgui_font_weight_heavy,
|
|
drgui_font_weight_extra_heavy,
|
|
|
|
drgui_font_weight_normal = drgui_font_weight_medium,
|
|
drgui_font_weight_default = drgui_font_weight_medium
|
|
|
|
} drgui_font_weight;
|
|
|
|
/// Font slants.
|
|
typedef enum
|
|
{
|
|
drgui_font_slant_none,
|
|
drgui_font_slant_italic,
|
|
drgui_font_slant_oblique
|
|
|
|
} drgui_font_slant;
|
|
|
|
/// Image formats.
|
|
typedef enum
|
|
{
|
|
drgui_image_format_rgba8,
|
|
drgui_image_format_bgra8,
|
|
drgui_image_format_argb8,
|
|
} drgui_image_format;
|
|
|
|
|
|
/// Font metrics.
|
|
struct drgui_font_metrics
|
|
{
|
|
int ascent;
|
|
int descent;
|
|
int lineHeight;
|
|
int spaceWidth;
|
|
};
|
|
|
|
/// Glyph metrics.
|
|
struct drgui_glyph_metrics
|
|
{
|
|
int width;
|
|
int height;
|
|
int originX;
|
|
int originY;
|
|
int advanceX;
|
|
int advanceY;
|
|
};
|
|
|
|
|
|
/// Structure representing an RGBA color. Color components are specified in the range of 0 - 255.
|
|
struct drgui_color
|
|
{
|
|
drgui_byte r;
|
|
drgui_byte g;
|
|
drgui_byte b;
|
|
drgui_byte a;
|
|
};
|
|
|
|
/// Structure representing a rectangle.
|
|
struct drgui_rect
|
|
{
|
|
float left;
|
|
float top;
|
|
float right;
|
|
float bottom;
|
|
};
|
|
|
|
|
|
#define DRGUI_IMAGE_DRAW_BACKGROUND (1 << 0)
|
|
#define DRGUI_IMAGE_HINT_NO_ALPHA (1 << 1)
|
|
#define DRGUI_IMAGE_DRAW_BOUNDS (1 << 2)
|
|
#define DRGUI_IMAGE_CLIP_BOUNDS (1 << 3) //< Clips the image to the bounds
|
|
#define DRGUI_IMAGE_ALIGN_CENTER (1 << 4)
|
|
|
|
#define DRGUI_READ (1 << 0)
|
|
#define DRGUI_WRITE (1 << 1)
|
|
|
|
#define DRGUI_FONT_NO_CLEARTYPE (1 << 0)
|
|
|
|
typedef struct
|
|
{
|
|
/// The destination position on the x axis. This is ignored if the DR2D_IMAGE_ALIGN_CENTER option is set.
|
|
float dstX;
|
|
|
|
/// The destination position on the y axis. This is ignored if the DR2D_IMAGE_ALIGN_CENTER option is set.
|
|
float dstY;
|
|
|
|
/// The destination width.
|
|
float dstWidth;
|
|
|
|
/// The destination height.
|
|
float dstHeight;
|
|
|
|
|
|
/// The source offset on the x axis.
|
|
float srcX;
|
|
|
|
/// The source offset on the y axis.
|
|
float srcY;
|
|
|
|
/// The source width.
|
|
float srcWidth;
|
|
|
|
/// The source height.
|
|
float srcHeight;
|
|
|
|
|
|
/// The position of the destination's bounds on the x axis.
|
|
float dstBoundsX;
|
|
|
|
/// The position of the destination's bounds on the y axis.
|
|
float dstBoundsY;
|
|
|
|
/// The width of the destination's bounds.
|
|
float dstBoundsWidth;
|
|
|
|
/// The height of the destination's bounds.
|
|
float dstBoundsHeight;
|
|
|
|
|
|
/// The foreground tint color. This is not applied to the background color, and the alpha component is ignored.
|
|
drgui_color foregroundTint;
|
|
|
|
/// The background color. Only used if the DR2D_IMAGE_DRAW_BACKGROUND option is set.
|
|
drgui_color backgroundColor;
|
|
|
|
/// The bounds color. This color is used for the region of the bounds that sit on the outside of the destination rectangle. This will
|
|
/// usually be set to the same value as backgroundColor, but it could also be used to draw a border around the image.
|
|
drgui_color boundsColor;
|
|
|
|
|
|
/// Flags for controlling how the image should be drawn.
|
|
unsigned int options;
|
|
|
|
} drgui_draw_image_args;
|
|
|
|
|
|
typedef void (* drgui_callback)();
|
|
|
|
typedef void (* drgui_on_move_proc) (drgui_element* pElement, float newRelativePosX, float newRelativePosY);
|
|
typedef void (* drgui_on_size_proc) (drgui_element* pElement, float newWidth, float newHeight);
|
|
typedef void (* drgui_on_mouse_enter_proc) (drgui_element* pElement);
|
|
typedef void (* drgui_on_mouse_leave_proc) (drgui_element* pElement);
|
|
typedef void (* drgui_on_mouse_move_proc) (drgui_element* pElement, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
typedef void (* drgui_on_mouse_button_down_proc) (drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
typedef void (* drgui_on_mouse_button_up_proc) (drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
typedef void (* drgui_on_mouse_button_dblclick_proc)(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
typedef void (* drgui_on_mouse_wheel_proc) (drgui_element* pElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
typedef void (* drgui_on_key_down_proc) (drgui_element* pElement, drgui_key key, int stateFlags);
|
|
typedef void (* drgui_on_key_up_proc) (drgui_element* pElement, drgui_key key, int stateFlags);
|
|
typedef void (* drgui_on_printable_key_down_proc) (drgui_element* pElement, unsigned int character, int stateFlags);
|
|
typedef void (* drgui_on_paint_proc) (drgui_element* pElement, drgui_rect relativeRect, void* pPaintData);
|
|
typedef void (* drgui_on_dirty_proc) (drgui_element* pElement, drgui_rect relativeRect);
|
|
typedef bool (* drgui_on_hittest_proc) (drgui_element* pElement, float relativePosX, float relativePosY);
|
|
typedef void (* drgui_on_capture_mouse_proc) (drgui_element* pElement);
|
|
typedef void (* drgui_on_release_mouse_proc) (drgui_element* pElement);
|
|
typedef void (* drgui_on_capture_keyboard_proc) (drgui_element* pElement, drgui_element* pPrevCapturedElement);
|
|
typedef void (* drgui_on_release_keyboard_proc) (drgui_element* pElement, drgui_element* pNewCapturedElement);
|
|
typedef void (* drgui_on_change_cursor_proc) (drgui_element* pElement, drgui_cursor_type cursor);
|
|
typedef void (* drgui_on_delete_element_proc) (drgui_element* pElement);
|
|
typedef void (* drgui_on_log) (drgui_context* pContext, const char* message);
|
|
|
|
typedef void (* drgui_draw_begin_proc) (void* pPaintData);
|
|
typedef void (* drgui_draw_end_proc) (void* pPaintData);
|
|
typedef void (* drgui_set_clip_proc) (drgui_rect relativeRect, void* pPaintData);
|
|
typedef void (* drgui_get_clip_proc) (drgui_rect* pRectOut, void* pPaintData);
|
|
typedef void (* drgui_draw_line_proc) (float startX, float startY, float endX, float endY, float width, drgui_color color, void* pPaintData);
|
|
typedef void (* drgui_draw_rect_proc) (drgui_rect relativeRect, drgui_color color, void* pPaintData);
|
|
typedef void (* drgui_draw_rect_outline_proc) (drgui_rect relativeRect, drgui_color color, float outlineWidth, void* pPaintData);
|
|
typedef void (* drgui_draw_rect_with_outline_proc) (drgui_rect relativeRect, drgui_color color, float outlineWidth, drgui_color outlineColor, void* pPaintData);
|
|
typedef void (* drgui_draw_round_rect_proc) (drgui_rect relativeRect, drgui_color color, float radius, void* pPaintData);
|
|
typedef void (* drgui_draw_round_rect_outline_proc) (drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, void* pPaintData);
|
|
typedef void (* drgui_draw_round_rect_with_outline_proc) (drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, drgui_color outlineColor, void* pPaintData);
|
|
typedef void (* drgui_draw_text_proc) (drgui_resource font, const char* text, int textLengthInBytes, float posX, float posY, drgui_color color, drgui_color backgroundColor, void* pPaintData);
|
|
typedef void (* drgui_draw_image_proc) (drgui_resource image, drgui_draw_image_args* pArgs, void* pPaintData);
|
|
|
|
typedef drgui_resource (* drgui_create_font_proc) (void* pPaintingContext, const char* family, unsigned int size, drgui_font_weight weight, drgui_font_slant slant, float rotation, unsigned int flags);
|
|
typedef void (* drgui_delete_font_proc) (drgui_resource font);
|
|
typedef unsigned int (* drgui_get_font_size_proc) (drgui_resource font);
|
|
typedef bool (* drgui_get_font_metrics_proc) (drgui_resource font, drgui_font_metrics* pMetricsOut);
|
|
typedef bool (* drgui_get_glyph_metrics_proc) (drgui_resource font, unsigned int utf32, drgui_glyph_metrics* pMetricsOut);
|
|
typedef bool (* drgui_measure_string_proc) (drgui_resource font, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut);
|
|
typedef bool (* drgui_get_text_cursor_position_from_point_proc)(drgui_resource font, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut);
|
|
typedef bool (* drgui_get_text_cursor_position_from_char_proc) (drgui_resource font, const char* text, size_t characterIndex, float* pTextCursorPosXOut);
|
|
|
|
typedef drgui_resource (* drgui_create_image_proc) (void* pPaintingContext, unsigned int width, unsigned int height, drgui_image_format format, unsigned int stride, const void* pImageData);
|
|
typedef void (* drgui_delete_image_proc) (drgui_resource image);
|
|
typedef drgui_image_format (* drgui_get_optimal_image_format_proc)(void* pPaintingContext);
|
|
typedef void (* drgui_get_image_size_proc) (drgui_resource image, unsigned int* pWidthOut, unsigned int* pHeightOut);
|
|
typedef void* (* drgui_map_image_data_proc) (drgui_resource image, unsigned int accessFlags);
|
|
typedef void (* drgui_unmap_image_data_proc) (drgui_resource image);
|
|
|
|
typedef bool (* drgui_visible_iteration_proc)(drgui_element* pElement, drgui_rect *pRelativeRect, void* pUserData);
|
|
|
|
|
|
// Key state flags.
|
|
#define DRGUI_MOUSE_BUTTON_LEFT_DOWN (1 << 0)
|
|
#define DRGUI_MOUSE_BUTTON_RIGHT_DOWN (1 << 1)
|
|
#define DRGUI_MOUSE_BUTTON_MIDDLE_DOWN (1 << 2)
|
|
#define DRGUI_MOUSE_BUTTON_4_DOWN (1 << 3)
|
|
#define DRGUI_MOUSE_BUTTON_5_DOWN (1 << 4)
|
|
#define DRGUI_KEY_STATE_SHIFT_DOWN (1 << 5) // Whether or not a shift key is down at the time the input event is handled.
|
|
#define DRGUI_KEY_STATE_CTRL_DOWN (1 << 6) // Whether or not a ctrl key is down at the time the input event is handled.
|
|
#define DRGUI_KEY_STATE_ALT_DOWN (1 << 7) // Whether or not an alt key is down at the time the input event is handled.
|
|
#define DRGUI_KEY_STATE_AUTO_REPEATED (1 << 31) // Whether or not the key press is generated due to auto-repeating. Only used with key down events.
|
|
|
|
// Common mouse buttons.
|
|
#define DRGUI_MOUSE_BUTTON_LEFT 1
|
|
#define DRGUI_MOUSE_BUTTON_RIGHT 2
|
|
#define DRGUI_MOUSE_BUTTON_MIDDLE 3
|
|
|
|
// Common key codes.
|
|
#define DRGUI_BACKSPACE 0xff08
|
|
#define DRGUI_SHIFT 0xff10
|
|
#define DRGUI_ESCAPE 0xff1b
|
|
#define DRGUI_PAGE_UP 0xff55
|
|
#define DRGUI_PAGE_DOWN 0xff56
|
|
#define DRGUI_END 0xff57
|
|
#define DRGUI_HOME 0xff50
|
|
#define DRGUI_ARROW_LEFT 0x8fb
|
|
#define DRGUI_ARROW_UP 0x8fc
|
|
#define DRGUI_ARROW_RIGHT 0x8fd
|
|
#define DRGUI_ARROW_DOWN 0x8fe
|
|
#define DRGUI_DELETE 0xffff
|
|
#define DRGUI_F1 0xffbe
|
|
#define DRGUI_F2 0xffbf
|
|
#define DRGUI_F3 0xffc0
|
|
#define DRGUI_F4 0xffc1
|
|
#define DRGUI_F5 0xffc2
|
|
#define DRGUI_F6 0xffc3
|
|
#define DRGUI_F7 0xffc4
|
|
#define DRGUI_F8 0xffc5
|
|
#define DRGUI_F9 0xffc6
|
|
#define DRGUI_F10 0xffc7
|
|
#define DRGUI_F11 0xffc8
|
|
#define DRGUI_F12 0xffc9
|
|
|
|
static size_t drgui_strcpy(char* dst, size_t dstSize, const char* src)
|
|
{
|
|
if (strcpy_s(dst, dstSize, src) == 0) {
|
|
return strlen(dst);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline size_t drgui_key_to_string(drgui_key key, char* strOut, size_t strOutSize)
|
|
{
|
|
if (strOut == NULL || strOutSize == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (strOutSize == 1) {
|
|
strOut[0] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
|
|
switch (key)
|
|
{
|
|
case DRGUI_BACKSPACE: return drgui_strcpy(strOut, strOutSize, "Backspace");
|
|
case DRGUI_SHIFT: return drgui_strcpy(strOut, strOutSize, "Shift");
|
|
case DRGUI_ESCAPE: return drgui_strcpy(strOut, strOutSize, "Escape");
|
|
case DRGUI_PAGE_UP: return drgui_strcpy(strOut, strOutSize, "Page Up");
|
|
case DRGUI_PAGE_DOWN: return drgui_strcpy(strOut, strOutSize, "Page Down");
|
|
case DRGUI_END: return drgui_strcpy(strOut, strOutSize, "End");
|
|
case DRGUI_HOME: return drgui_strcpy(strOut, strOutSize, "Home");
|
|
case DRGUI_ARROW_LEFT: return drgui_strcpy(strOut, strOutSize, "Arrow Left");
|
|
case DRGUI_ARROW_UP: return drgui_strcpy(strOut, strOutSize, "Arrow Up");
|
|
case DRGUI_ARROW_RIGHT: return drgui_strcpy(strOut, strOutSize, "Arrow Right");
|
|
case DRGUI_ARROW_DOWN: return drgui_strcpy(strOut, strOutSize, "Arrow Down");
|
|
case DRGUI_DELETE: return drgui_strcpy(strOut, strOutSize, "Delete");
|
|
case DRGUI_F1: return drgui_strcpy(strOut, strOutSize, "F1");
|
|
case DRGUI_F2: return drgui_strcpy(strOut, strOutSize, "F2");
|
|
case DRGUI_F3: return drgui_strcpy(strOut, strOutSize, "F3");
|
|
case DRGUI_F4: return drgui_strcpy(strOut, strOutSize, "F4");
|
|
case DRGUI_F5: return drgui_strcpy(strOut, strOutSize, "F5");
|
|
case DRGUI_F6: return drgui_strcpy(strOut, strOutSize, "F6");
|
|
case DRGUI_F7: return drgui_strcpy(strOut, strOutSize, "F7");
|
|
case DRGUI_F8: return drgui_strcpy(strOut, strOutSize, "F8");
|
|
case DRGUI_F9: return drgui_strcpy(strOut, strOutSize, "F9");
|
|
case DRGUI_F10: return drgui_strcpy(strOut, strOutSize, "F10");
|
|
case DRGUI_F11: return drgui_strcpy(strOut, strOutSize, "F11");
|
|
case DRGUI_F12: return drgui_strcpy(strOut, strOutSize, "F12");
|
|
}
|
|
|
|
if (key >= 32 && key <= 126) {
|
|
strOut[0] = (char)key;
|
|
strOut[1] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
// TODO: Non-ascii characters.
|
|
return 0;
|
|
}
|
|
|
|
static inline drgui_key drgui_key_parse(const char* str)
|
|
{
|
|
if (str == NULL || str[0] == '\0') {
|
|
return 0;
|
|
}
|
|
|
|
if (_stricmp(str, "backspace") == 0) return DRGUI_BACKSPACE;
|
|
if (_stricmp(str, "shift") == 0) return DRGUI_SHIFT;
|
|
if (_stricmp(str, "escape") == 0) return DRGUI_ESCAPE;
|
|
if (_stricmp(str, "page up") == 0 || _stricmp(str, "pageup") == 0) return DRGUI_PAGE_UP;
|
|
if (_stricmp(str, "page down") == 0 || _stricmp(str, "pagedown") == 0) return DRGUI_PAGE_DOWN;
|
|
if (_stricmp(str, "end") == 0) return DRGUI_END;
|
|
if (_stricmp(str, "home") == 0) return DRGUI_HOME;
|
|
if (_stricmp(str, "arrow left") == 0 || _stricmp(str, "arrowleft") == 0) return DRGUI_ARROW_LEFT;
|
|
if (_stricmp(str, "arrow up") == 0 || _stricmp(str, "arrowup") == 0) return DRGUI_ARROW_UP;
|
|
if (_stricmp(str, "arrow right") == 0 || _stricmp(str, "arrowright") == 0) return DRGUI_ARROW_RIGHT;
|
|
if (_stricmp(str, "arrow down") == 0 || _stricmp(str, "arrowdown") == 0) return DRGUI_ARROW_DOWN;
|
|
if (_stricmp(str, "delete") == 0) return DRGUI_BACKSPACE;
|
|
|
|
if (str[0] == 'F' || str[0] == 'f') {
|
|
if (str[1] == '1') {
|
|
if (str[2] == '\0') {
|
|
return DRGUI_F1;
|
|
} else {
|
|
if (str[2] == '0' && str[2] == '\0') return DRGUI_F10;
|
|
if (str[2] == '1' && str[2] == '\0') return DRGUI_F11;
|
|
if (str[2] == '2' && str[2] == '\0') return DRGUI_F12;
|
|
}
|
|
}
|
|
if (str[1] == '2' && str[2] == '\0') return DRGUI_F2;
|
|
if (str[1] == '3' && str[2] == '\0') return DRGUI_F3;
|
|
if (str[1] == '4' && str[2] == '\0') return DRGUI_F4;
|
|
if (str[1] == '5' && str[2] == '\0') return DRGUI_F5;
|
|
if (str[1] == '6' && str[2] == '\0') return DRGUI_F6;
|
|
if (str[1] == '7' && str[2] == '\0') return DRGUI_F7;
|
|
if (str[1] == '8' && str[2] == '\0') return DRGUI_F8;
|
|
if (str[1] == '9' && str[2] == '\0') return DRGUI_F9;
|
|
}
|
|
|
|
|
|
// ASCII characters.
|
|
if (str[0] >= 32 && str[0] <= 126 && str[1] == '\0') {
|
|
return str[0];
|
|
}
|
|
|
|
if (_stricmp(str, "tab") == 0) {
|
|
return '\t';
|
|
}
|
|
|
|
|
|
// TODO: Non-ascii characters.
|
|
return 0;
|
|
}
|
|
|
|
|
|
/// Structure containing callbacks for painting routines.
|
|
struct drgui_painting_callbacks
|
|
{
|
|
drgui_draw_begin_proc drawBegin;
|
|
drgui_draw_end_proc drawEnd;
|
|
|
|
drgui_set_clip_proc setClip;
|
|
drgui_get_clip_proc getClip;
|
|
|
|
drgui_draw_line_proc drawLine;
|
|
drgui_draw_rect_proc drawRect;
|
|
drgui_draw_rect_outline_proc drawRectOutline;
|
|
drgui_draw_rect_with_outline_proc drawRectWithOutline;
|
|
drgui_draw_round_rect_proc drawRoundRect;
|
|
drgui_draw_round_rect_outline_proc drawRoundRectOutline;
|
|
drgui_draw_round_rect_with_outline_proc drawRoundRectWithOutline;
|
|
drgui_draw_text_proc drawText;
|
|
drgui_draw_image_proc drawImage;
|
|
|
|
drgui_create_font_proc createFont;
|
|
drgui_delete_font_proc deleteFont;
|
|
drgui_get_font_size_proc getFontSize;
|
|
drgui_get_font_metrics_proc getFontMetrics;
|
|
drgui_get_glyph_metrics_proc getGlyphMetrics;
|
|
drgui_measure_string_proc measureString;
|
|
drgui_get_text_cursor_position_from_point_proc getTextCursorPositionFromPoint;
|
|
drgui_get_text_cursor_position_from_char_proc getTextCursorPositionFromChar;
|
|
|
|
drgui_create_image_proc createImage;
|
|
drgui_delete_image_proc deleteImage;
|
|
drgui_get_optimal_image_format_proc getOptimalImageFormat;
|
|
drgui_get_image_size_proc getImageSize;
|
|
drgui_map_image_data_proc mapImageData;
|
|
drgui_unmap_image_data_proc unmapImageData;
|
|
};
|
|
|
|
struct drgui_image
|
|
{
|
|
/// A pointer to the context that owns this image.
|
|
drgui_context* pContext;
|
|
|
|
/// The resource handle that is passed around to the callback functions.
|
|
drgui_resource hResource;
|
|
};
|
|
|
|
struct drgui_font
|
|
{
|
|
/// A pointer to the context that owns this font.
|
|
drgui_context* pContext;
|
|
|
|
/// The font family.
|
|
char family[DRGUI_MAX_FONT_FAMILY_LENGTH];
|
|
|
|
/// The base size of the font. This is set to the value that was used to create the font in the first place.
|
|
unsigned int size;
|
|
|
|
/// The font's weight.
|
|
drgui_font_weight weight;
|
|
|
|
/// The fon't slant.
|
|
drgui_font_slant slant;
|
|
|
|
/// The fon't rotation.
|
|
float rotation;
|
|
|
|
/// The font's flags. Can be a combination of the following:
|
|
/// DRGUI_FONT_NO_CLEARTYPE
|
|
unsigned int flags;
|
|
|
|
/// The internal font. This is created by the rendering backend.
|
|
drgui_resource internalFont;
|
|
};
|
|
|
|
|
|
struct drgui_element
|
|
{
|
|
/// A pointer to the context that owns this element. This should never be null for valid elements.
|
|
drgui_context* pContext;
|
|
|
|
|
|
/// A pointer to the parent element. This can be null in which case this element is the parent.
|
|
drgui_element* pParent;
|
|
|
|
/// A pointer to the first child element.
|
|
drgui_element* pFirstChild;
|
|
|
|
/// A pointer to the last child element.
|
|
drgui_element* pLastChild;
|
|
|
|
/// A pointer to the next sibling element.
|
|
drgui_element* pNextSibling;
|
|
|
|
/// A pointer ot the previous sibing element.
|
|
drgui_element* pPrevSibling;
|
|
|
|
|
|
/// A pointer to the next dead element. When an element is deleted during an event handler it is not deleted straight away but
|
|
/// rather at the end of the current batch of event processing. Dead elements are stored in a linked list, with this pointer
|
|
/// acting as the link between items. This will be null if the element is the last in the list, or is not marked as dead. Note
|
|
/// that this should not be used to check if the element is marked as dead - use the IS_ELEMENT_DEAD flag instead.
|
|
drgui_element* pNextDeadElement;
|
|
|
|
|
|
/// The type of the element, as a string. This is only every used by the host application, and is intended to be used as way
|
|
/// to selectively perform certain operations on specific types of GUI elements.
|
|
char type[64];
|
|
|
|
|
|
/// The absolute position of the element on the x axis. A position of 0 is the left side of the surface it is attached to.
|
|
float absolutePosX;
|
|
|
|
/// The absolute position of the element on the y axis. A position of 0 is the top of the surface it is attached to.
|
|
float absolutePosY;
|
|
|
|
/// The width of the element.
|
|
float width;
|
|
|
|
/// The height of the element.
|
|
float height;
|
|
|
|
|
|
/// The cursor. Defaults to drge_cursor_default.
|
|
drgui_cursor_type cursor;
|
|
|
|
/// Boolean flags.
|
|
unsigned int flags;
|
|
|
|
// The region of the element that's dirty.
|
|
drgui_rect dirtyRect;
|
|
|
|
|
|
/// The function to call when the element's relative position moves.
|
|
drgui_on_move_proc onMove;
|
|
|
|
/// The function to call when the element's size changes.
|
|
drgui_on_size_proc onSize;
|
|
|
|
/// The function to call when the mouse enters the given element.
|
|
drgui_on_mouse_enter_proc onMouseEnter;
|
|
|
|
/// The function to call when the mouse leaves the given element.
|
|
drgui_on_mouse_leave_proc onMouseLeave;
|
|
|
|
/// The function to call when the mouse is moved while over the element.
|
|
drgui_on_mouse_move_proc onMouseMove;
|
|
|
|
/// The function to call when a mouse buttonis pressed while over the element.
|
|
drgui_on_mouse_button_down_proc onMouseButtonDown;
|
|
|
|
/// The function to call when a mouse button is released while over the element.
|
|
drgui_on_mouse_button_up_proc onMouseButtonUp;
|
|
|
|
/// The function to call when a mouse button is double-clicked while over the element.
|
|
drgui_on_mouse_button_dblclick_proc onMouseButtonDblClick;
|
|
|
|
/// The function to call when the mouse wheel it turned while over the element.
|
|
drgui_on_mouse_wheel_proc onMouseWheel;
|
|
|
|
/// The function to call when a key on the keyboard is pressed or auto-repeated.
|
|
drgui_on_key_down_proc onKeyDown;
|
|
|
|
/// The function to call when a key on the keyboard is released.
|
|
drgui_on_key_up_proc onKeyUp;
|
|
|
|
/// The function to call when a printable character is pressed or auto-repeated. This would be used for text editing.
|
|
drgui_on_printable_key_down_proc onPrintableKeyDown;
|
|
|
|
/// The function to call when the paint event is received.
|
|
drgui_on_paint_proc onPaint;
|
|
|
|
/// The function to call when the element is marked as dirty.
|
|
drgui_on_dirty_proc onDirty;
|
|
|
|
/// The function to call when a hit test needs to be performed.
|
|
drgui_on_hittest_proc onHitTest;
|
|
|
|
/// The event handler to call when an element receives the mouse focus.
|
|
drgui_on_capture_mouse_proc onCaptureMouse;
|
|
|
|
/// The event handler to call when an element loses the mouse focus.
|
|
drgui_on_release_mouse_proc onReleaseMouse;
|
|
|
|
/// The event handler to call when an element receives the keyboard focus.
|
|
drgui_on_capture_keyboard_proc onCaptureKeyboard;
|
|
|
|
/// The event handler to call when an element loses the keyboard focus.
|
|
drgui_on_release_keyboard_proc onReleaseKeyboard;
|
|
|
|
|
|
/// The size of the extra data.
|
|
size_t extraDataSize;
|
|
|
|
/// A pointer to the extra data.
|
|
drgui_byte pExtraData[1];
|
|
};
|
|
|
|
struct drgui_context
|
|
{
|
|
/// The paiting context.
|
|
void* pPaintingContext;
|
|
|
|
/// The painting callbacks.
|
|
drgui_painting_callbacks paintingCallbacks;
|
|
|
|
|
|
/// The inbound event counter. This is incremented with drgui_begin_inbound_event() and decremented with
|
|
/// drgui_end_inbound_event(). We use this to determine whether or not an inbound event is being processed.
|
|
int inboundEventCounter;
|
|
|
|
/// The outbound event counter that we use as the "lock" for outbound events. All outbound events are posted from
|
|
/// inbound events, and all inbound events are already synchronized so we don't need to use a mutex. This is mainly
|
|
/// used as a way to check for erroneous outbound event generation.
|
|
int outboundEventLockCounter;
|
|
|
|
/// A pointer to the first element that has been marked as dead. Elements marked as dead are stored as a linked list.
|
|
drgui_element* pFirstDeadElement;
|
|
|
|
/// A pointer to the element that is sitting directly under the mouse. This is updated on every inbound mouse move event
|
|
/// and is used for determining when a mouse enter/leave event needs to be posted.
|
|
drgui_element* pElementUnderMouse;
|
|
|
|
/// A pointer to the element with the mouse capture.
|
|
drgui_element* pElementWithMouseCapture;
|
|
|
|
/// A pointer to the element with the keyboard focus.
|
|
drgui_element* pElementWithKeyboardCapture;
|
|
|
|
/// A pointer to the element that wants the keyboard focus. If for some reason an element isn't able to immediately
|
|
/// capture the keyboard (such as while in the middle of a release_keyboard event handler) this will be set to that
|
|
/// particular element. This will then be used to capture the keyboard at a later time when it is able.
|
|
drgui_element* pElementWantingKeyboardCapture;
|
|
|
|
/// The current cursor.
|
|
drgui_cursor_type currentCursor;
|
|
|
|
/// Boolean flags.
|
|
unsigned int flags;
|
|
|
|
|
|
/// The global event callback to call when an element is marked as dirty.
|
|
drgui_on_dirty_proc onGlobalDirty;
|
|
|
|
/// The global event handler to call when an element captures the mouse.
|
|
drgui_on_capture_mouse_proc onGlobalCaptureMouse;
|
|
|
|
/// The global event handler to call when an element releases the mouse.
|
|
drgui_on_release_mouse_proc onGlobalReleaseMouse;
|
|
|
|
/// The global event handler to call when an element captures the keyboard.
|
|
drgui_on_capture_keyboard_proc onGlobalCaptureKeyboard;
|
|
|
|
/// The global event handler to call when an element releases the keyboard.
|
|
drgui_on_release_keyboard_proc onGlobalReleaseKeyboard;
|
|
|
|
/// The global event handler to call when the system cursor needs to change.
|
|
drgui_on_change_cursor_proc onChangeCursor;
|
|
|
|
/// The function to call when an element is deleted.
|
|
drgui_on_delete_element_proc onDeleteElement;
|
|
|
|
|
|
/// The function to call when a log message is posted.
|
|
drgui_on_log onLog;
|
|
|
|
|
|
|
|
/// A pointer to the top level element that was passed in from the last inbound mouse move event.
|
|
drgui_element* pLastMouseMoveTopLevelElement;
|
|
|
|
/// The position of the mouse that was passed in from the last inbound mouse move event.
|
|
float lastMouseMovePosX;
|
|
float lastMouseMovePosY;
|
|
|
|
|
|
// A pointer to the list of dirty elements.
|
|
drgui_element** ppDirtyElements;
|
|
|
|
// The size of the buffer containing the dirty elements.
|
|
size_t dirtyElementBufferSize;
|
|
|
|
// The number of dirty top-level elements.
|
|
size_t dirtyElementCount;
|
|
|
|
/// The counter to use when determining whether or not an on_dirty event needs to be posted. This is incremented with
|
|
/// drgui_begin_auto_dirty() and decremented with drgui_end_auto_dirty(). When the counter is decremented and hits
|
|
/// zero, the on_dirty event will be posted.
|
|
unsigned int dirtyCounter;
|
|
};
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// CORE API
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
/// Creates a context.
|
|
drgui_context* drgui_create_context();
|
|
|
|
/// Deletes a context and everything that it created.
|
|
void drgui_delete_context(drgui_context* pContext);
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// Events
|
|
|
|
/// Posts a mouse leave inbound event.
|
|
///
|
|
/// @remarks
|
|
/// The intention behind this event is to allow the application to let dr_gui know that the mouse have left the window. Since dr_gui does
|
|
/// not have any notion of a window it must rely on the host application to notify it.
|
|
void drgui_post_inbound_event_mouse_leave(drgui_element* pTopLevelElement);
|
|
|
|
/// Posts a mouse move inbound event.
|
|
void drgui_post_inbound_event_mouse_move(drgui_element* pTopLevelElement, int mousePosX, int mousePosY, int stateFlags);
|
|
|
|
/// Posts a mouse button down inbound event.
|
|
void drgui_post_inbound_event_mouse_button_down(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags);
|
|
|
|
/// Posts a mouse button up inbound event.
|
|
void drgui_post_inbound_event_mouse_button_up(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags);
|
|
|
|
/// Posts a mouse button double-clicked inbound event.
|
|
void drgui_post_inbound_event_mouse_button_dblclick(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags);
|
|
|
|
/// Posts a mouse wheel inbound event.
|
|
void drgui_post_inbound_event_mouse_wheel(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags);
|
|
|
|
/// Posts a key down inbound event.
|
|
void drgui_post_inbound_event_key_down(drgui_context* pContext, drgui_key key, int stateFlags);
|
|
|
|
/// Posts a key up inbound event.
|
|
void drgui_post_inbound_event_key_up(drgui_context* pContext, drgui_key key, int stateFlags);
|
|
|
|
/// Posts a printable key down inbound event.
|
|
///
|
|
/// @remarks
|
|
/// The \c character argument should be a UTF-32 code point.
|
|
void drgui_post_inbound_event_printable_key_down(drgui_context* pContext, unsigned int character, int stateFlags);
|
|
|
|
|
|
/// Registers the global on_dirty event callback.
|
|
///
|
|
/// @remarks
|
|
/// This is called whenever a region of an element is marked as dirty and allows an application to mark the region of the
|
|
/// container window as dirty to trigger an operating system level repaint of the window.
|
|
void drgui_set_global_on_dirty(drgui_context* pContext, drgui_on_dirty_proc onDirty);
|
|
|
|
/// Registers the global on_capture_mouse event callback.
|
|
///
|
|
/// @remarks
|
|
/// This is called whenever an element receives an the mouse capture and allows an application to do operating system level
|
|
/// mouse captures against the container window or whatnot.
|
|
/// @par
|
|
/// The advantage of using a global event callback is that it can be set once at the context level rather than many times
|
|
/// at the element level.
|
|
void drgui_set_global_on_capture_mouse(drgui_context* pContext, drgui_on_capture_mouse_proc onCaptureMouse);
|
|
|
|
/// Registers the global on_release_mouse event callback.
|
|
///
|
|
/// @remarks
|
|
/// This is called whenever an element loses an the mouse capture and allows an application to do operating system level
|
|
/// mouse releases against the container window or whatnot.
|
|
/// @par
|
|
/// The advantage of using a global event callback is that it can be set once at the context level rather than many times
|
|
/// at the element level.
|
|
void drgui_set_global_on_release_mouse(drgui_context* pContext, drgui_on_release_mouse_proc onReleaseMouse);
|
|
|
|
/// Registers the global on_capture_keyboard event callback.
|
|
///
|
|
/// @remarks
|
|
/// This is called whenever an element receives an the keyboard capture and allows an application to do an operating system level
|
|
/// keyboard focus against the container window or whatnot.
|
|
/// @par
|
|
/// The advantage of using a global event callback is that it can be set once at the context level rather than many times
|
|
/// at the element level.
|
|
void drgui_set_global_on_capture_keyboard(drgui_context* pContext, drgui_on_capture_keyboard_proc onCaptureKeyboard);
|
|
|
|
/// Registers the global on_release_keyboard event callback.
|
|
///
|
|
/// @remarks
|
|
/// This is called whenever an element loses an the keyboard capture and allows an application to do an operating system level
|
|
/// keyboard release against the container window or whatnot.
|
|
/// @par
|
|
/// The advantage of using a global event callback is that it can be set once at the context level rather than many times
|
|
/// at the element level.
|
|
void drgui_set_global_on_release_keyboard(drgui_context* pContext, drgui_on_capture_keyboard_proc onReleaseKeyboard);
|
|
|
|
/// Sets the global on_change_cursor event callback.
|
|
///
|
|
/// @remarks
|
|
/// This is called whenever the operating system needs to change the cursor.
|
|
void drgui_set_global_on_change_cursor(drgui_context* pContext, drgui_on_change_cursor_proc onChangeCursor);
|
|
|
|
/// Sets the function to call when an element is deleted.
|
|
void drgui_set_on_delete_element(drgui_context* pContext, drgui_on_delete_element_proc onDeleteElement);
|
|
|
|
|
|
/// Registers the callback to call when a log message is posted.
|
|
void drgui_set_on_log(drgui_context* pContext, drgui_on_log onLog);
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// Elements
|
|
|
|
/// Creates an element.
|
|
drgui_element* drgui_create_element(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData);
|
|
|
|
/// Deletes and element.
|
|
void drgui_delete_element(drgui_element* pElement);
|
|
|
|
|
|
/// Retrieves the size of the extra data of the given element, in bytes.
|
|
size_t drgui_get_extra_data_size(drgui_element* pElement);
|
|
|
|
/// Retrieves a pointer to the extra data of the given element.
|
|
void* drgui_get_extra_data(drgui_element* pElement);
|
|
|
|
|
|
/// Sets the type of the element.
|
|
///
|
|
/// The type name cannot be more than 63 characters in length.
|
|
bool drgui_set_type(drgui_element* pElement, const char* type);
|
|
|
|
/// Retrieves the type fo the element.
|
|
const char* drgui_get_type(drgui_element* pElement);
|
|
|
|
/// Determines whether or not the given element is of the given type.
|
|
bool drgui_is_of_type(drgui_element* pElement, const char* type);
|
|
|
|
|
|
/// Hides the given element.
|
|
void drgui_hide(drgui_element *pElement);
|
|
|
|
/// Shows the given element.
|
|
void drgui_show(drgui_element* pElement);
|
|
|
|
/// Determines whether or not the element is marked as visible.
|
|
///
|
|
/// @remarks
|
|
/// This is a direct accessor for the internal visible flag of the element and is not recursive. Thus, if this element is
|
|
/// marked as visible, but it's parent is invisible, it will still return true. Use drgui_is_visible_recursive() to do
|
|
/// a recursive visibility check.
|
|
bool drgui_is_visible(const drgui_element* pElement);
|
|
|
|
/// Recursively determines whether or not the element is marked as visible.
|
|
bool drgui_is_visible_recursive(const drgui_element* pElement);
|
|
|
|
|
|
/// Disables clipping against the parent for the given element.
|
|
void drgui_disable_clipping(drgui_element* pElement);
|
|
|
|
/// Enables clipping against the parent for the given element.
|
|
void drgui_enable_clipping(drgui_element* pElement);
|
|
|
|
/// Determines whether or not clipping is enabled for the given element.
|
|
bool drgui_is_clipping_enabled(const drgui_element* pElement);
|
|
|
|
|
|
/// Sets the element that should receive all future mouse related events.
|
|
///
|
|
/// @remarks
|
|
/// Release the mouse capture with drgui_release_mosue().
|
|
void drgui_capture_mouse(drgui_element* pElement);
|
|
|
|
/// Releases the mouse capture.
|
|
void drgui_release_mouse(drgui_context* pContext);
|
|
|
|
/// Releases the mouse capture without posting the global-scoped event. Should only be used in very specific cases, usually in combination with awkward interop with the window system.
|
|
void drgui_release_mouse_no_global_notify(drgui_context* pContext);
|
|
|
|
/// Retrieves a pointer to the element with the mouse capture.
|
|
drgui_element* drgui_get_element_with_mouse_capture(drgui_context* pContext);
|
|
|
|
/// Determines whether or not the given element has the mouse capture.
|
|
bool drgui_has_mouse_capture(drgui_element* pElement);
|
|
|
|
|
|
/// Sets the element that should receive all future keyboard related events.
|
|
///
|
|
/// @remarks
|
|
/// Releases the keyboard capture with drgui_release_keyboard().
|
|
void drgui_capture_keyboard(drgui_element* pElement);
|
|
|
|
/// Releases the keyboard capture.
|
|
void drgui_release_keyboard(drgui_context* pContext);
|
|
|
|
/// Releases the keyboard capture without posting the global-scoped event. Should only be used in very specific cases, usually in combination with awkward interop with the window system.
|
|
void drgui_release_keyboard_no_global_notify(drgui_context* pContext);
|
|
|
|
/// Retrieves a pointer to the element with the keyboard capture.
|
|
drgui_element* drgui_get_element_with_keyboard_capture(drgui_context* pContext);
|
|
|
|
/// Determines whether or not the given element has the keyboard capture.
|
|
bool drgui_has_keyboard_capture(drgui_element* pElement);
|
|
|
|
|
|
/// Sets the cursor to use when the mouse enters the given GUI element.
|
|
void drgui_set_cursor(drgui_element* pElement, drgui_cursor_type cursor);
|
|
|
|
/// Retrieves the cursor to use when the mouse enters the given GUI element.
|
|
drgui_cursor_type drgui_get_cursor(drgui_element* pElement);
|
|
|
|
|
|
//// Events ////
|
|
|
|
/// Registers the on_move event callback.
|
|
void drgui_set_on_move(drgui_element* pElement, drgui_on_move_proc callback);
|
|
|
|
/// Registers the on_size event callback.
|
|
void drgui_set_on_size(drgui_element* pElement, drgui_on_size_proc callback);
|
|
|
|
/// Registers the on_mouse_enter event callback.
|
|
void drgui_set_on_mouse_enter(drgui_element* pElement, drgui_on_mouse_enter_proc callback);
|
|
|
|
/// Registers the on_mouse_leave event callback.
|
|
void drgui_set_on_mouse_leave(drgui_element* pElement, drgui_on_mouse_leave_proc callback);
|
|
|
|
/// Registers the on_mouse_move event callback.
|
|
void drgui_set_on_mouse_move(drgui_element* pElement, drgui_on_mouse_move_proc callback);
|
|
|
|
/// Registers the on_mouse_button_down event callback.
|
|
void drgui_set_on_mouse_button_down(drgui_element* pElement, drgui_on_mouse_button_down_proc callback);
|
|
|
|
/// Registers the on_mouse_button_up event callback.
|
|
void drgui_set_on_mouse_button_up(drgui_element* pElement, drgui_on_mouse_button_up_proc callback);
|
|
|
|
/// Registers the on_mouse_button_down event callback.
|
|
void drgui_set_on_mouse_button_dblclick(drgui_element* pElement, drgui_on_mouse_button_dblclick_proc callback);
|
|
|
|
/// Registers the on_mouse_wheel event callback.
|
|
void drgui_set_on_mouse_wheel(drgui_element* pElement, drgui_on_mouse_wheel_proc callback);
|
|
|
|
/// Registers the on_key_down event callback.
|
|
void drgui_set_on_key_down(drgui_element* pElement, drgui_on_key_down_proc callback);
|
|
|
|
/// Registers the on_key_up event callback.
|
|
void drgui_set_on_key_up(drgui_element* pElement, drgui_on_key_up_proc callback);
|
|
|
|
/// Registers the on_printable_key_down event callback.
|
|
void drgui_set_on_printable_key_down(drgui_element* pElement, drgui_on_printable_key_down_proc callback);
|
|
|
|
/// Registers the on_paint event callback.
|
|
void drgui_set_on_paint(drgui_element* pElement, drgui_on_paint_proc callback);
|
|
|
|
/// Registers the on_dirty event callback.
|
|
void drgui_set_on_dirty(drgui_element* pElement, drgui_on_dirty_proc callback);
|
|
|
|
/// Registers the on_hittest event callback.
|
|
void drgui_set_on_hittest(drgui_element* pElement, drgui_on_hittest_proc callback);
|
|
|
|
/// Registers the on_capture_mouse event callback.
|
|
void drgui_set_on_capture_mouse(drgui_element* pElement, drgui_on_capture_mouse_proc callback);
|
|
|
|
/// Registers the on_release_mouse event callback.
|
|
void drgui_set_on_release_mouse(drgui_element* pElement, drgui_on_release_mouse_proc callback);
|
|
|
|
/// Registers the on_capture_keyboard event callback.
|
|
void drgui_set_on_capture_keyboard(drgui_element* pElement, drgui_on_capture_keyboard_proc callback);
|
|
|
|
/// Registers the on_release_keyboard event callback.
|
|
void drgui_set_on_release_keyboard(drgui_element* pElement, drgui_on_release_keyboard_proc callback);
|
|
|
|
|
|
|
|
//// Containment and Hit Detection ////
|
|
|
|
/// Determines whether or not the given point is inside the bounds of the given element.
|
|
///
|
|
/// @remarks
|
|
/// This only checks if the point is inside the bounds of the element and does not take hit testing into account. This difference
|
|
/// with this one and drgui_is_point_inside_element() is that the latter will use hit testing.
|
|
bool drgui_is_point_inside_element_bounds(const drgui_element* pElement, float absolutePosX, float absolutePosY);
|
|
|
|
/// Determines whether or not the given point is inside the given element.
|
|
///
|
|
/// @remarks
|
|
/// This will use hit testing to determine whether or not the point is inside the element.
|
|
bool drgui_is_point_inside_element(drgui_element* pElement, float absolutePosX, float absolutePosY);
|
|
|
|
/// Finds the element under the given point taking mouse pass-through and hit testing into account.
|
|
drgui_element* drgui_find_element_under_point(drgui_element* pTopLevelElement, float absolutePosX, float absolutePosY);
|
|
|
|
/// Determines whether or not the given element is currently sitting directly under the mouse.
|
|
bool drgui_is_element_under_mouse(drgui_element* pTopLevelElement);
|
|
|
|
|
|
|
|
//// Hierarchy ////
|
|
|
|
// Retrieves the parent of the given element.
|
|
drgui_element* drgui_get_parent(drgui_element* pChildElement);
|
|
|
|
/// Detaches the given element from it's parent.
|
|
void drgui_detach(drgui_element* pChildElement);
|
|
|
|
/// Attaches the given element as a child of the given parent element, and appends it to the end of the children list.
|
|
void drgui_append(drgui_element* pChildElement, drgui_element* pParentElement);
|
|
|
|
/// Attaches the given element as a child of the given parent element, and prepends it to the end of the children list.
|
|
void drgui_prepend(drgui_element* pChildElement, drgui_element* pParentElement);
|
|
|
|
/// Appends the given element to the given sibling.
|
|
void drgui_append_sibling(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo);
|
|
|
|
/// Prepends the given element to the given sibling.
|
|
void drgui_prepend_sibling(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo);
|
|
|
|
/// Retrieves a pointer to the given element's top-level ancestor.
|
|
///
|
|
/// @remarks
|
|
/// If pElement is the top level element, the return value will be pElement.
|
|
drgui_element* drgui_find_top_level_element(drgui_element* pElement);
|
|
|
|
/// Determines whether or not the given element is the parent of the other.
|
|
///
|
|
/// @remarks
|
|
/// This is not recursive. Use drgui_is_ancestor() to do a recursive traversal.
|
|
bool drgui_is_parent(drgui_element* pParentElement, drgui_element* pChildElement);
|
|
|
|
/// Determines whether or not the given element is a child of the other.
|
|
///
|
|
/// @remarks
|
|
/// This is not recursive. Use drgui_is_descendant() to do a recursive traversal.
|
|
bool drgui_is_child(drgui_element* pChildElement, drgui_element* pParentElement);
|
|
|
|
/// Determines whether or not the given element is an ancestor of the other.
|
|
bool drgui_is_ancestor(drgui_element* pAncestorElement, drgui_element* pChildElement);
|
|
|
|
/// Determines whether or not the given element is a descendant of the other.
|
|
bool drgui_is_descendant(drgui_element* pChildElement, drgui_element* pAncestorElement);
|
|
|
|
/// Determines whether or not the given element is itself or a descendant.
|
|
bool drgui_is_self_or_ancestor(drgui_element* pAncestorElement, drgui_element* pChildElement);
|
|
|
|
/// Determines whether or not the given element is itself or a descendant.
|
|
bool drgui_is_self_or_descendant(drgui_element* pChildElement, drgui_element* pAncestorElement);
|
|
|
|
|
|
|
|
//// Layout ////
|
|
|
|
/// Sets the absolute position of the given element.
|
|
void drgui_set_absolute_position(drgui_element* pElement, float positionX, float positionY);
|
|
|
|
/// Retrieves the absolute position of the given element.
|
|
void drgui_get_absolute_position(const drgui_element* pElement, float* positionXOut, float* positionYOut);
|
|
float drgui_get_absolute_position_x(const drgui_element* pElement);
|
|
float drgui_get_absolute_position_y(const drgui_element* pElement);
|
|
|
|
|
|
/// Sets the relative position of the given element.
|
|
void drgui_set_relative_position(drgui_element* pElement, float relativePosX, float relativePosY);
|
|
|
|
/// Retrieves the relative position of the given element.
|
|
void drgui_get_relative_position(const drgui_element* pElement, float* relativePosXOut, float* relativePosYOut);
|
|
float drgui_get_relative_position_x(const drgui_element* pElement);
|
|
float drgui_get_relative_position_y(const drgui_element* pElement);
|
|
|
|
|
|
/// Sets the size of the given element.
|
|
void drgui_set_size(drgui_element* pElement, float width, float height);
|
|
|
|
/// Retrieves the size of the given element.
|
|
void drgui_get_size(const drgui_element* pElement, float* widthOut, float* heightOut);
|
|
float drgui_get_width(const drgui_element* pElement);
|
|
float drgui_get_height(const drgui_element* pElement);
|
|
|
|
|
|
|
|
/// Retrieves the absolute rectangle for the given element.
|
|
drgui_rect drgui_get_absolute_rect(const drgui_element* pElement);
|
|
|
|
/// Retrieves the relative rectangle for the given element.
|
|
drgui_rect drgui_get_relative_rect(const drgui_element* pElement);
|
|
|
|
/// Retrieves the local rectangle for the given element.
|
|
///
|
|
/// @remarks
|
|
/// The local rectangle is equivalent to drgui_make_rect(0, 0, drgui_get_width(pElement), drgui_get_height(pElement));
|
|
drgui_rect drgui_get_local_rect(const drgui_element* pElement);
|
|
|
|
|
|
|
|
//// Painting ////
|
|
|
|
/// Registers the custom painting callbacks.
|
|
///
|
|
/// @remarks
|
|
/// This can only be called once, so it should always be done after initialization. This will fail if called
|
|
/// more than once.
|
|
bool drgui_register_painting_callbacks(drgui_context* pContext, void* pPaintingContext, drgui_painting_callbacks callbacks);
|
|
|
|
|
|
/// Performs a recursive traversal of all visible elements in the given rectangle.
|
|
///
|
|
/// @param pParentElement [in] A pointer to the element to iterate.
|
|
///
|
|
/// @remarks
|
|
/// pParentElement will be included in the iteration is it is within the rectangle.
|
|
/// @par
|
|
/// The rectangle should be relative to pParentElement.
|
|
/// @par
|
|
/// The iteration callback function takes a pointer to a rectangle structure that represents the visible portion of the
|
|
/// element. This pointer can be modified by the callback to create an adjusted rectangle which can be used for clipping.
|
|
bool drgui_iterate_visible_elements(drgui_element* pParentElement, drgui_rect relativeRect, drgui_visible_iteration_proc callback, void* pUserData);
|
|
|
|
|
|
/// Disable's automatic dirtying of elements.
|
|
void drgui_disable_auto_dirty(drgui_context* pContext);
|
|
|
|
/// Enable's automatic dirtying of elements.
|
|
void drgui_enable_auto_dirty(drgui_context* pContext);
|
|
|
|
/// Determines whether or not automatic dirtying is enabled.
|
|
bool drgui_is_auto_dirty_enabled(drgui_context* pContext);
|
|
|
|
|
|
/// Begins accumulating a dirty rectangle.
|
|
///
|
|
/// Returns a pointer to the top level element that was made dirty.
|
|
drgui_element* drgui_begin_dirty(drgui_element* pElement);
|
|
|
|
/// Ends accumulating a dirty rectangle, and requests a redraw from the backend if the counter reaches zero.
|
|
void drgui_end_dirty(drgui_element* pElement);
|
|
|
|
/// Marks a region of the given element as dirty.
|
|
///
|
|
/// @remarks
|
|
/// This will not redraw the element immediately, but instead post a paint event.
|
|
void drgui_dirty(drgui_element* pElement, drgui_rect relativeRect);
|
|
|
|
|
|
/// Draws the given element.
|
|
///
|
|
/// @remarks
|
|
/// Do not call this on one element, then again on it's children. Any children that fall inside the specified
|
|
/// rectangle will also be redrawn.
|
|
/// @par
|
|
/// This will call painting event handlers which will give the application time to do custom drawing.
|
|
/// @par
|
|
/// When using easy_draw to do drawing, pPaintData must be set to a pointer to the relevant easydraw_surface object.
|
|
void drgui_draw(drgui_element* pElement, drgui_rect relativeRect, void* pPaintData);
|
|
|
|
/// Retrieves the current clipping rectangle.
|
|
void drgui_get_clip(drgui_element* pElement, drgui_rect* pRelativeRect, void* pPaintData);
|
|
|
|
/// Sets the clipping rectangle to apply to all future draw operations on this element.
|
|
void drgui_set_clip(drgui_element* pElement, drgui_rect relativeRect, void* pPaintData);
|
|
|
|
/// Draws a rectangle on the given element.
|
|
void drgui_draw_rect(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, void* pPaintData);
|
|
|
|
/// Draws the outline of a rectangle on the given element.
|
|
void drgui_draw_rect_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float outlineWidth, void* pPaintData);
|
|
|
|
/// Draws a filled rectangle with an outline on the given element.
|
|
void drgui_draw_rect_with_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float outlineWidth, drgui_color outlineColor, void* pPaintData);
|
|
|
|
/// Draws a rectangle with rounded corners on the given element.
|
|
void drgui_draw_round_rect(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, void* pPaintData);
|
|
|
|
/// Draws the outline of a rectangle with rounded corners on the given element.
|
|
void drgui_draw_round_rect_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, void* pPaintData);
|
|
|
|
/// Draws a filled rectangle and it's outline with rounded corners on the given element.
|
|
void drgui_draw_round_rect_with_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, drgui_color outlineColor, void* pPaintData);
|
|
|
|
/// Draws a run of text on the given element.
|
|
///
|
|
/// @remarks
|
|
/// This does not do any complex formatting like multiple lines and whatnot. Complex formatting can be achieved with multiple
|
|
/// calls to this function.
|
|
/// @par
|
|
/// \c textSizeInBytes can be -1 in which case the text string is treated as null terminated.
|
|
void drgui_draw_text(drgui_element* pElement, drgui_font* pFont, const char* text, int textLengthInBytes, float posX, float posY, drgui_color color, drgui_color backgroundColor, void* pPaintData);
|
|
|
|
/// Draws an image.
|
|
void drgui_draw_image(drgui_element* pElement, drgui_image* pImage, drgui_draw_image_args* pArgs, void* pPaintData);
|
|
|
|
|
|
/// Creates a font resource.
|
|
drgui_font* drgui_create_font(drgui_context* pContext, const char* family, unsigned int size, drgui_font_weight weight, drgui_font_slant slant, float rotation, unsigned int flags);
|
|
|
|
/// Deletes a font resource.
|
|
void drgui_delete_font(drgui_font* pFont);
|
|
|
|
/// Retrieves the metrics of the given font.
|
|
bool drgui_get_font_metrics(drgui_font* pFont, drgui_font_metrics* pMetricsOut);
|
|
|
|
/// Retrieves the metrics of the glyph for the given character when rendered with the given font.
|
|
bool drgui_get_glyph_metrics(drgui_font* pFont, unsigned int utf32, drgui_glyph_metrics* pMetricsOut);
|
|
|
|
/// Retrieves the dimensions of the given string when drawn with the given font.
|
|
///
|
|
/// @remarks
|
|
/// When the length of the text is 0, the width will be set to 0 and the height will be set to the line height.
|
|
bool drgui_measure_string(drgui_font* pFont, const char* text, size_t textLengthInBytes, float* pWidthOut, float* pHeightOut);
|
|
|
|
/// Retrieves the position to place a text cursor based on the given point for the given string when drawn with the given font.
|
|
bool drgui_get_text_cursor_position_from_point(drgui_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut);
|
|
|
|
/// Retrieves the position to palce a text cursor based on the character at the given index for the given string when drawn with the given font.
|
|
bool drgui_get_text_cursor_position_from_char(drgui_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut);
|
|
|
|
|
|
|
|
/// Creates an image that can be passed to drgui_draw_image().
|
|
///
|
|
/// @remarks
|
|
/// The dimensions and format of an image are immutable. If these need to change, then the image needs to be deleted and re-created.
|
|
/// @par
|
|
/// If pData is NULL, the default image data is undefined.
|
|
/// @par
|
|
/// If stride is set to 0, it is assumed to be tightly packed.
|
|
/// @par
|
|
/// Use drgui_map_image_data() and drgui_unmap_image_data() to update or retrieve image data.
|
|
drgui_image* drgui_create_image(drgui_context* pContext, unsigned int width, unsigned int height, drgui_image_format format, unsigned int stride, const void* pData);
|
|
|
|
/// Deletes the given image.
|
|
void drgui_delete_image(drgui_image* pImage);
|
|
|
|
/// Retrieves the size of the given image.
|
|
void drgui_get_image_size(drgui_image* pImage, unsigned int* pWidthOut, unsigned int* pHeightOut);
|
|
|
|
/// Retrieves the optimal image format for the given context.
|
|
drgui_image_format drgui_get_optimal_image_format(drgui_context* pContext);
|
|
|
|
/// Retrieves a pointer to a buffer representing the given image's data.
|
|
///
|
|
/// Call drgui_unmap_image_data() when you are done with this function.
|
|
///
|
|
/// Use this function to access an image's data. The returned pointer does not necessarilly point to the image's actual data, so when
|
|
/// writing to this pointer, nothing is actually updated until drgui_unmap_image_data() is called.
|
|
///
|
|
/// The returned data will contain the image data at the time of the mapping.
|
|
void* drgui_map_image_data(drgui_image* pImage, unsigned int accessFlags);
|
|
|
|
/// Unmaps the given image data.
|
|
void drgui_unmap_image_data(drgui_image* pImage);
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// HIGH-LEVEL API
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
//// Hit Testing and Layout ////
|
|
|
|
/// An on_size event callback that resizes every child element to that of the parent.
|
|
void drgui_on_size_fit_children_to_parent(drgui_element* pElement, float newWidth, float newHeight);
|
|
|
|
/// An on_hit_test event callback that can be used to always fail the mouse hit test.
|
|
bool drgui_pass_through_hit_test(drgui_element* pElement, float mousePosX, float mousePosY);
|
|
|
|
|
|
//// Painting ////
|
|
|
|
/// Draws a border around the given element.
|
|
void drgui_draw_border(drgui_element* pElement, float borderWidth, drgui_color color, void* pUserData);
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// UTILITY API
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
/// Creates a color object from a set of RGBA color components.
|
|
drgui_color drgui_rgba(drgui_byte r, drgui_byte g, drgui_byte b, drgui_byte a);
|
|
|
|
/// Creates a color object from a set of RGB color components.
|
|
drgui_color drgui_rgb(drgui_byte r, drgui_byte g, drgui_byte b);
|
|
|
|
/// Clamps the given rectangle to another.
|
|
drgui_rect drgui_clamp_rect(drgui_rect rect, drgui_rect other);
|
|
|
|
/// Clamps the given rectangle to the given element and returns whether or not any of it is contained within the element's rectangle.
|
|
bool drgui_clamp_rect_to_element(const drgui_element* pElement, drgui_rect* pRelativeRect);
|
|
|
|
/// Converts the given rectangle from absolute to relative to the given element.
|
|
drgui_rect drgui_make_rect_relative(const drgui_element* pElement, drgui_rect* pRect);
|
|
|
|
/// Converts the given rectangle from relative to absolute based on the given element.
|
|
drgui_rect drgui_make_rect_absolute(const drgui_element* pElement, drgui_rect* pRect);
|
|
|
|
/// Converts the given point from absolute to relative to the given element.
|
|
void drgui_make_point_relative(const drgui_element* pElement, float* positionX, float* positionY);
|
|
|
|
/// Converts the given point from relative to absolute based on the given element.
|
|
void drgui_make_point_absolute(const drgui_element* pElement, float* positionX, float* positionY);
|
|
|
|
/// Creates a drgui_rect object.
|
|
drgui_rect drgui_make_rect(float left, float top, float right, float bottom);
|
|
|
|
/// Creates an inside-out rectangle.
|
|
///
|
|
/// @remarks
|
|
/// An inside our rectangle is a negative-dimension rectangle with each edge at the extreme edges. The left edge will be at the
|
|
/// right-most side and the right edge will be at the left-most side. The same applies for the top and bottom edges.
|
|
drgui_rect drgui_make_inside_out_rect();
|
|
|
|
/// Expands the given rectangle on all sides by the given amount.
|
|
///
|
|
/// @remarks
|
|
/// This will increase the width and height of the rectangle by <amount> x 2.
|
|
/// @par
|
|
/// The growth amount can be negative, in which case it will be shrunk. Note that this does not do any checking to ensure the rectangle
|
|
/// contains positive dimensions after a shrink.
|
|
drgui_rect drgui_grow_rect(drgui_rect rect, float amount);
|
|
|
|
/// Scales the given rectangle.
|
|
///
|
|
/// @param scaleX [in] The scale to apply to <left> and <right>
|
|
/// @param scaleY [in] The scale to apply to <top> and <bottom>
|
|
///
|
|
/// @remarks
|
|
/// This will modify the <left> and <top> properties which means the rectangle will change position. To adjust only the size, scale the
|
|
/// rectangle manually.
|
|
drgui_rect drgui_scale_rect(drgui_rect rect, float scaleX, float scaleY);
|
|
|
|
/// Offsets the given rectangle.
|
|
drgui_rect drgui_offset_rect(drgui_rect rect, float offsetX, float offsetY);
|
|
|
|
/// Creates a rectangle that contains both of the given rectangles.
|
|
drgui_rect drgui_rect_union(drgui_rect rect0, drgui_rect rect1);
|
|
|
|
/// Determines whether or not the given rectangle contains the given point.
|
|
///
|
|
/// @remarks
|
|
/// An important not here is that if the position is sitting on the right or bottom border, false will be returned. If, however, the point
|
|
/// is sitting on the left or top border, true will be returned. The reason for this is that elements may sit exactly side-by-side with
|
|
/// each other, and if we use this function to determine if a point is contained within an element (which we do), we would end up having
|
|
/// this return true for both elements, which we don't want.
|
|
bool drgui_rect_contains_point(drgui_rect rect, float posX, float posY);
|
|
|
|
/// Determines whether or not two rectangles are equal.
|
|
bool drgui_rect_equal(drgui_rect rect0, drgui_rect rect1);
|
|
|
|
/// Determines whether or not the given rectangle has any volume (width and height > 0).
|
|
bool drgui_rect_has_volume(drgui_rect rect);
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// EASY_DRAW-SPECIFIC API
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
#ifndef DRGUI_NO_DR_2D
|
|
|
|
/// A covenience function for creating a new context and registering the easy_draw painting callbacks.
|
|
///
|
|
/// @remarks
|
|
/// This is equivalent to drgui_create_context() followed by drgui_register_dr_2d_callbacks().
|
|
drgui_context* drgui_create_context_dr_2d(dr2d_context* pDrawingContext);
|
|
|
|
/// Registers the drawing callbacks for use with easy_draw.
|
|
///
|
|
/// @remarks
|
|
/// The user data of each callback is assumed to be a pointer to an easydraw_surface object.
|
|
void drgui_register_dr_2d_callbacks(drgui_context* pContext, dr2d_context* pDrawingContext);
|
|
|
|
#endif
|
|
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif //dr_gui_h
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// IMPLEMENTATION
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#ifdef DR_GUI_IMPLEMENTATION
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <float.h>
|
|
#include <math.h>
|
|
|
|
#ifndef DRGUI_PRIVATE
|
|
#define DRGUI_PRIVATE static
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// PRIVATE CORE API
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// Context Flags
|
|
#define IS_CONTEXT_DEAD (1U << 0)
|
|
#define IS_AUTO_DIRTY_DISABLED (1U << 1)
|
|
#define IS_RELEASING_KEYBOARD (1U << 2)
|
|
|
|
// Element Flags
|
|
#define IS_ELEMENT_HIDDEN (1U << 0)
|
|
#define IS_ELEMENT_CLIPPING_DISABLED (1U << 1)
|
|
#define IS_ELEMENT_DEAD (1U << 31)
|
|
|
|
|
|
static int drgui__strcpy_s(char* dst, size_t dstSizeInBytes, const char* src)
|
|
{
|
|
#ifdef _MSC_VER
|
|
return strcpy_s(dst, dstSizeInBytes, src);
|
|
#else
|
|
if (dst == 0) {
|
|
return EINVAL;
|
|
}
|
|
if (dstSizeInBytes == 0) {
|
|
return ERANGE;
|
|
}
|
|
if (src == 0) {
|
|
dst[0] = '\0';
|
|
return EINVAL;
|
|
}
|
|
|
|
size_t i;
|
|
for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) {
|
|
dst[i] = src[i];
|
|
}
|
|
|
|
if (i < dstSizeInBytes) {
|
|
dst[i] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
dst[0] = '\0';
|
|
return ERANGE;
|
|
#endif
|
|
}
|
|
|
|
int drgui__strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count)
|
|
{
|
|
#ifdef _MSC_VER
|
|
return strncpy_s(dst, dstSizeInBytes, src, count);
|
|
#else
|
|
if (dst == 0) {
|
|
return EINVAL;
|
|
}
|
|
if (dstSizeInBytes == 0) {
|
|
return EINVAL;
|
|
}
|
|
if (src == 0) {
|
|
dst[0] = '\0';
|
|
return EINVAL;
|
|
}
|
|
|
|
size_t maxcount = count;
|
|
if (count == ((size_t)-1) || count >= dstSizeInBytes) { // -1 = _TRUNCATE
|
|
maxcount = dstSizeInBytes - 1;
|
|
}
|
|
|
|
size_t i;
|
|
for (i = 0; i < maxcount && src[i] != '\0'; ++i) {
|
|
dst[i] = src[i];
|
|
}
|
|
|
|
if (src[i] == '\0' || i == count || count == ((size_t)-1)) {
|
|
dst[i] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
dst[0] = '\0';
|
|
return ERANGE;
|
|
#endif
|
|
}
|
|
|
|
|
|
/// Increments the inbound event counter
|
|
///
|
|
/// @remarks
|
|
/// This is called from every drgui_post_inbound_event_*() function and is used to keep track of whether or
|
|
/// not an inbound event is being processed. We need to track this because if we are in the middle of event
|
|
/// processing and an element is deleted, we want to delay it's deletion until the end of the event processing.
|
|
/// @par
|
|
/// Use drgui_end_inbound_event() to decrement the counter.
|
|
void drgui_begin_inbound_event(drgui_context* pContext);
|
|
|
|
/// Decrements the inbound event counter.
|
|
///
|
|
/// @remarks
|
|
/// This is called from every drgui_post_inbound_event_*() function.
|
|
/// @par
|
|
/// When the internal counter reaches zero, deleted elements will be garbage collected.
|
|
void drgui_end_inbound_event(drgui_context* pContext);
|
|
|
|
/// Determines whether or not inbound events are being processed.
|
|
///
|
|
/// @remarks
|
|
/// This is used to determine whether or not an element can be deleted immediately or should be garbage collected
|
|
/// at the end of event processing.
|
|
bool drgui_is_handling_inbound_event(const drgui_context* pContext);
|
|
|
|
|
|
/// Increments the outbound event counter.
|
|
///
|
|
/// @remarks
|
|
/// This will validate that the given element is allowed to have an event posted. When false is returned, nothing
|
|
/// will have been locked and the outbound event should be cancelled.
|
|
/// @par
|
|
/// This will return false if the given element has been marked as dead, or if there is some other reason it should
|
|
/// not be receiving events.
|
|
bool drgui_begin_outbound_event(drgui_element* pElement);
|
|
|
|
/// Decrements the outbound event counter.
|
|
void drgui_end_outbound_event(drgui_element* pElement);
|
|
|
|
/// Determines whether or not and outbound event is being processed.
|
|
bool drgui_is_handling_outbound_event(drgui_context* pContext);
|
|
|
|
|
|
/// Marks the given element as dead.
|
|
void drgui_mark_element_as_dead(drgui_element* pElement);
|
|
|
|
/// Determines whether or not the given element is marked as dead.
|
|
bool drgui_is_element_marked_as_dead(const drgui_element* pElement);
|
|
|
|
/// Deletes every element that has been marked as dead.
|
|
void drgui_delete_elements_marked_as_dead(drgui_context* pContext);
|
|
|
|
|
|
/// Marks the given context as deleted.
|
|
void drgui_mark_context_as_dead(drgui_context* pContext);
|
|
|
|
/// Determines whether or not the given context is marked as dead.
|
|
bool drgui_is_context_marked_as_dead(const drgui_context* pContext);
|
|
|
|
|
|
/// Deletes the given context for real.
|
|
///
|
|
/// If a context is deleted during the processing of an inbound event it will not be deleting immediately - this
|
|
/// will delete the context for real.
|
|
void drgui_delete_context_for_real(drgui_context* pContext);
|
|
|
|
/// Deletes the given element for real.
|
|
///
|
|
/// Sometimes an element will not be deleted straight away but instead just marked as dead. We use this to delete
|
|
/// the given element for real.
|
|
void drgui_delete_element_for_real(drgui_element* pElement);
|
|
|
|
|
|
/// Orphans the given element without triggering a redraw of the parent nor the child.
|
|
void drgui_detach_without_redraw(drgui_element* pChildElement);
|
|
|
|
/// Appends the given element without first detaching it from the old parent, nor does it post a redraw.
|
|
void drgui_append_without_detach_or_redraw(drgui_element* pChildElement, drgui_element* pParentElement);
|
|
|
|
/// Appends the given element without first detaching it from the old parent.
|
|
void drgui_append_without_detach(drgui_element* pChildElement, drgui_element* pParentElement);
|
|
|
|
/// Prepends the given element without first detaching it from the old parent, nor does it post a redraw.
|
|
void drgui_prepend_without_detach_or_redraw(drgui_element* pChildElement, drgui_element* pParentElement);
|
|
|
|
/// Prepends the given element without first detaching it from the old parent.
|
|
void drgui_prepend_without_detach(drgui_element* pChildElement, drgui_element* pParentElement);
|
|
|
|
/// Appends an element to another as it's sibling, but does not detach it from the previous parent nor trigger a redraw.
|
|
void drgui_append_sibling_without_detach_or_redraw(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo);
|
|
|
|
/// Appends an element to another as it's sibling, but does not detach it from the previous parent.
|
|
void drgui_append_sibling_without_detach(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo);
|
|
|
|
/// Prepends an element to another as it's sibling, but does not detach it from the previous parent nor trigger a redraw.
|
|
void drgui_prepend_sibling_without_detach_or_redraw(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo);
|
|
|
|
/// Prepends an element to another as it's sibling, but does not detach it from the previous parent.
|
|
void drgui_prepend_sibling_without_detach(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo);
|
|
|
|
|
|
/// Begins accumulating an invalidation rectangle.
|
|
void drgui_begin_auto_dirty(drgui_element* pElement);
|
|
|
|
/// Ends accumulating the invalidation rectangle and posts on_dirty is auto-dirty is enabled.
|
|
void drgui_end_auto_dirty(drgui_element* pElement);
|
|
|
|
/// Marks the given region of the given top level element as dirty, but only if automatic dirtying is enabled.
|
|
///
|
|
/// @remarks
|
|
/// This is equivalent to drgui_begin_auto_dirty() immediately followed by drgui_end_auto_dirty().
|
|
void drgui_auto_dirty(drgui_element* pTopLevelElement, drgui_rect rect);
|
|
|
|
|
|
/// Recursively applies the given offset to the absolute positions of the children of the given element.
|
|
///
|
|
/// @remarks
|
|
/// This is called when the absolute position of an element is changed.
|
|
void drgui_apply_offset_to_children_recursive(drgui_element* pParentElement, float offsetX, float offsetY);
|
|
|
|
|
|
/// The function to call when the mouse may have entered into a new element.
|
|
void drgui_update_mouse_enter_and_leave_state(drgui_context* pContext, drgui_element* pNewElementUnderMouse);
|
|
|
|
|
|
/// Functions for posting outbound events.
|
|
void drgui_post_outbound_event_move(drgui_element* pElement, float newRelativePosX, float newRelativePosY);
|
|
void drgui_post_outbound_event_size(drgui_element* pElement, float newWidth, float newHeight);
|
|
void drgui_post_outbound_event_mouse_enter(drgui_element* pElement);
|
|
void drgui_post_outbound_event_mouse_leave(drgui_element* pElement);
|
|
void drgui_post_outbound_event_mouse_move(drgui_element* pElement, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
void drgui_post_outbound_event_mouse_button_down(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
void drgui_post_outbound_event_mouse_button_up(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
void drgui_post_outbound_event_mouse_button_dblclick(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
void drgui_post_outbound_event_mouse_wheel(drgui_element* pElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
void drgui_post_outbound_event_key_down(drgui_element* pElement, drgui_key key, int stateFlags);
|
|
void drgui_post_outbound_event_key_up(drgui_element* pElement, drgui_key key, int stateFlags);
|
|
void drgui_post_outbound_event_printable_key_down(drgui_element* pElement, unsigned int character, int stateFlags);
|
|
void drgui_post_outbound_event_dirty(drgui_element* pElement, drgui_rect relativeRect);
|
|
void drgui_post_outbound_event_dirty_global(drgui_element* pElement, drgui_rect relativeRect);
|
|
void drgui_post_outbound_event_capture_mouse(drgui_element* pElement);
|
|
void drgui_post_outbound_event_capture_mouse_global(drgui_element* pElement);
|
|
void drgui_post_outbound_event_release_mouse(drgui_element* pElement);
|
|
void drgui_post_outbound_event_release_mouse_global(drgui_element* pElement);
|
|
void drgui_post_outbound_event_capture_keyboard(drgui_element* pElement, drgui_element* pPrevCapturedElement);
|
|
void drgui_post_outbound_event_capture_keyboard_global(drgui_element* pElement, drgui_element* pPrevCapturedElement);
|
|
void drgui_post_outbound_event_release_keyboard(drgui_element* pElement, drgui_element* pNewCapturedElement);
|
|
void drgui_post_outbound_event_release_keyboard_global(drgui_element* pElement, drgui_element* pNewCapturedElement);
|
|
|
|
/// Posts a log message.
|
|
void drgui_log(drgui_context* pContext, const char* message);
|
|
|
|
|
|
void drgui_begin_inbound_event(drgui_context* pContext)
|
|
{
|
|
assert(pContext != NULL);
|
|
|
|
pContext->inboundEventCounter += 1;
|
|
}
|
|
|
|
void drgui_end_inbound_event(drgui_context* pContext)
|
|
{
|
|
assert(pContext != NULL);
|
|
assert(pContext->inboundEventCounter > 0);
|
|
|
|
pContext->inboundEventCounter -= 1;
|
|
|
|
|
|
// Here is where we want to clean up any elements that are marked as dead. When events are being handled elements are not deleted
|
|
// immediately but instead only marked for deletion. This function will be called at the end of event processing which makes it
|
|
// an appropriate place for cleaning up dead elements.
|
|
if (!drgui_is_handling_inbound_event(pContext))
|
|
{
|
|
drgui_delete_elements_marked_as_dead(pContext);
|
|
|
|
// If the context has been marked for deletion than we will need to delete that too.
|
|
if (drgui_is_context_marked_as_dead(pContext))
|
|
{
|
|
drgui_delete_context_for_real(pContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool drgui_is_handling_inbound_event(const drgui_context* pContext)
|
|
{
|
|
assert(pContext != NULL);
|
|
|
|
return pContext->inboundEventCounter > 0;
|
|
}
|
|
|
|
|
|
|
|
bool drgui_begin_outbound_event(drgui_element* pElement)
|
|
{
|
|
assert(pElement != NULL);
|
|
assert(pElement->pContext != NULL);
|
|
|
|
|
|
// We want to cancel the outbound event if the element is marked as dead.
|
|
if (drgui_is_element_marked_as_dead(pElement)) {
|
|
drgui_log(pElement->pContext, "WARNING: Attemping to post an event to an element that is marked for deletion.");
|
|
return false;
|
|
}
|
|
|
|
|
|
// At this point everything should be fine so we just increment the count (which should never go above 1) and return true.
|
|
pElement->pContext->outboundEventLockCounter += 1;
|
|
|
|
return true;
|
|
}
|
|
|
|
void drgui_end_outbound_event(drgui_element* pElement)
|
|
{
|
|
assert(pElement != NULL);
|
|
assert(pElement->pContext != NULL);
|
|
assert(pElement->pContext->outboundEventLockCounter > 0);
|
|
|
|
pElement->pContext->outboundEventLockCounter -= 1;
|
|
}
|
|
|
|
bool drgui_is_handling_outbound_event(drgui_context* pContext)
|
|
{
|
|
assert(pContext != NULL);
|
|
return pContext->outboundEventLockCounter > 0;
|
|
}
|
|
|
|
|
|
void drgui_mark_element_as_dead(drgui_element* pElement)
|
|
{
|
|
assert(pElement != NULL);
|
|
assert(pElement->pContext != NULL);
|
|
|
|
pElement->flags |= IS_ELEMENT_DEAD;
|
|
|
|
|
|
if (pElement->pContext->pFirstDeadElement != NULL) {
|
|
pElement->pNextDeadElement = pElement->pContext->pFirstDeadElement;
|
|
}
|
|
|
|
pElement->pContext->pFirstDeadElement = pElement;
|
|
}
|
|
|
|
bool drgui_is_element_marked_as_dead(const drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return (pElement->flags & IS_ELEMENT_DEAD) != 0;
|
|
}
|
|
|
|
void drgui_delete_elements_marked_as_dead(drgui_context* pContext)
|
|
{
|
|
assert(pContext != NULL);
|
|
|
|
while (pContext->pFirstDeadElement != NULL)
|
|
{
|
|
drgui_element* pDeadElement = pContext->pFirstDeadElement;
|
|
pContext->pFirstDeadElement = pContext->pFirstDeadElement->pNextDeadElement;
|
|
|
|
drgui_delete_element_for_real(pDeadElement);
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_mark_context_as_dead(drgui_context* pContext)
|
|
{
|
|
assert(pContext != NULL);
|
|
assert(!drgui_is_context_marked_as_dead(pContext));
|
|
|
|
pContext->flags |= IS_CONTEXT_DEAD;
|
|
}
|
|
|
|
bool drgui_is_context_marked_as_dead(const drgui_context* pContext)
|
|
{
|
|
assert(pContext != NULL);
|
|
|
|
return (pContext->flags & IS_CONTEXT_DEAD) != 0;
|
|
}
|
|
|
|
|
|
|
|
void drgui_delete_context_for_real(drgui_context* pContext)
|
|
{
|
|
assert(pContext != NULL);
|
|
|
|
// All elements marked as dead need to be deleted.
|
|
drgui_delete_elements_marked_as_dead(pContext);
|
|
|
|
free(pContext);
|
|
}
|
|
|
|
void drgui_delete_element_for_real(drgui_element* pElementToDelete)
|
|
{
|
|
assert(pElementToDelete != NULL);
|
|
|
|
drgui_context* pContext = pElementToDelete->pContext;
|
|
|
|
// If the element is marked as dead
|
|
if (drgui_is_element_marked_as_dead(pElementToDelete)) {
|
|
if (pContext->pFirstDeadElement == pElementToDelete) {
|
|
pContext->pFirstDeadElement = pContext->pFirstDeadElement->pNextDeadElement;
|
|
} else {
|
|
drgui_element* pPrevDeadElement = pContext->pFirstDeadElement;
|
|
while (pPrevDeadElement != NULL) {
|
|
if (pPrevDeadElement->pNextDeadElement == pElementToDelete) {
|
|
break;
|
|
}
|
|
|
|
pPrevDeadElement = pPrevDeadElement->pNextDeadElement;
|
|
}
|
|
|
|
if (pPrevDeadElement != NULL) {
|
|
pElementToDelete->pNextDeadElement = pElementToDelete->pNextDeadElement;
|
|
}
|
|
}
|
|
}
|
|
|
|
free(pElementToDelete);
|
|
}
|
|
|
|
|
|
void drgui_detach_without_redraw(drgui_element* pElement)
|
|
{
|
|
if (pElement->pParent != NULL) {
|
|
if (pElement->pParent->pFirstChild == pElement) {
|
|
pElement->pParent->pFirstChild = pElement->pNextSibling;
|
|
}
|
|
|
|
if (pElement->pParent->pLastChild == pElement) {
|
|
pElement->pParent->pLastChild = pElement->pPrevSibling;
|
|
}
|
|
|
|
|
|
if (pElement->pPrevSibling != NULL) {
|
|
pElement->pPrevSibling->pNextSibling = pElement->pNextSibling;
|
|
}
|
|
|
|
if (pElement->pNextSibling != NULL) {
|
|
pElement->pNextSibling->pPrevSibling = pElement->pPrevSibling;
|
|
}
|
|
}
|
|
|
|
pElement->pParent = NULL;
|
|
pElement->pPrevSibling = NULL;
|
|
pElement->pNextSibling = NULL;
|
|
}
|
|
|
|
void drgui_append_without_detach_or_redraw(drgui_element* pChildElement, drgui_element* pParentElement)
|
|
{
|
|
pChildElement->pParent = pParentElement;
|
|
if (pChildElement->pParent != NULL) {
|
|
if (pChildElement->pParent->pLastChild != NULL) {
|
|
pChildElement->pPrevSibling = pChildElement->pParent->pLastChild;
|
|
pChildElement->pPrevSibling->pNextSibling = pChildElement;
|
|
}
|
|
|
|
if (pChildElement->pParent->pFirstChild == NULL) {
|
|
pChildElement->pParent->pFirstChild = pChildElement;
|
|
}
|
|
|
|
pChildElement->pParent->pLastChild = pChildElement;
|
|
}
|
|
}
|
|
|
|
void drgui_append_without_detach(drgui_element* pChildElement, drgui_element* pParentElement)
|
|
{
|
|
drgui_append_without_detach_or_redraw(pChildElement, pParentElement);
|
|
drgui_auto_dirty(pChildElement, drgui_make_rect(0, 0, pChildElement->width, pChildElement->height));
|
|
}
|
|
|
|
void drgui_prepend_without_detach_or_redraw(drgui_element* pChildElement, drgui_element* pParentElement)
|
|
{
|
|
pChildElement->pParent = pParentElement;
|
|
if (pChildElement->pParent != NULL) {
|
|
if (pChildElement->pParent->pFirstChild != NULL) {
|
|
pChildElement->pNextSibling = pChildElement->pParent->pFirstChild;
|
|
pChildElement->pNextSibling->pPrevSibling = pChildElement;
|
|
}
|
|
|
|
if (pChildElement->pParent->pLastChild == NULL) {
|
|
pChildElement->pParent->pLastChild = pChildElement;
|
|
}
|
|
|
|
pChildElement->pParent->pFirstChild = pChildElement;
|
|
}
|
|
}
|
|
|
|
void drgui_prepend_without_detach(drgui_element* pChildElement, drgui_element* pParentElement)
|
|
{
|
|
drgui_prepend_without_detach_or_redraw(pChildElement, pParentElement);
|
|
drgui_auto_dirty(pChildElement, drgui_make_rect(0, 0, pChildElement->width, pChildElement->height));
|
|
}
|
|
|
|
void drgui_append_sibling_without_detach_or_redraw(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo)
|
|
{
|
|
assert(pElementToAppend != NULL);
|
|
assert(pElementToAppendTo != NULL);
|
|
|
|
pElementToAppend->pParent = pElementToAppendTo->pParent;
|
|
if (pElementToAppend->pParent != NULL)
|
|
{
|
|
pElementToAppend->pNextSibling = pElementToAppendTo->pNextSibling;
|
|
pElementToAppend->pPrevSibling = pElementToAppendTo;
|
|
|
|
pElementToAppendTo->pNextSibling->pPrevSibling = pElementToAppend;
|
|
pElementToAppendTo->pNextSibling = pElementToAppend;
|
|
|
|
if (pElementToAppend->pParent->pLastChild == pElementToAppendTo) {
|
|
pElementToAppend->pParent->pLastChild = pElementToAppend;
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_append_sibling_without_detach(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo)
|
|
{
|
|
drgui_append_sibling_without_detach_or_redraw(pElementToAppend, pElementToAppendTo);
|
|
drgui_auto_dirty(pElementToAppend, drgui_make_rect(0, 0, pElementToAppend->width, pElementToAppend->height));
|
|
}
|
|
|
|
void drgui_prepend_sibling_without_detach_or_redraw(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo)
|
|
{
|
|
assert(pElementToPrepend != NULL);
|
|
assert(pElementToPrependTo != NULL);
|
|
|
|
pElementToPrepend->pParent = pElementToPrependTo->pParent;
|
|
if (pElementToPrepend->pParent != NULL)
|
|
{
|
|
pElementToPrepend->pPrevSibling = pElementToPrependTo->pNextSibling;
|
|
pElementToPrepend->pNextSibling = pElementToPrependTo;
|
|
|
|
pElementToPrependTo->pPrevSibling->pNextSibling = pElementToPrepend;
|
|
pElementToPrependTo->pNextSibling = pElementToPrepend;
|
|
|
|
if (pElementToPrepend->pParent->pFirstChild == pElementToPrependTo) {
|
|
pElementToPrepend->pParent->pFirstChild = pElementToPrepend;
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_prepend_sibling_without_detach(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo)
|
|
{
|
|
drgui_prepend_sibling_without_detach_or_redraw(pElementToPrepend, pElementToPrependTo);
|
|
drgui_auto_dirty(pElementToPrepend, drgui_make_rect(0, 0, pElementToPrepend->width, pElementToPrepend->height));
|
|
}
|
|
|
|
|
|
void drgui_begin_auto_dirty(drgui_element* pElement)
|
|
{
|
|
assert(pElement != NULL);
|
|
assert(pElement->pContext != NULL);
|
|
|
|
if (drgui_is_auto_dirty_enabled(pElement->pContext)) {
|
|
drgui_begin_dirty(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_end_auto_dirty(drgui_element* pElement)
|
|
{
|
|
assert(pElement != NULL);
|
|
|
|
drgui_context* pContext = pElement->pContext;
|
|
assert(pContext != NULL);
|
|
|
|
if (drgui_is_auto_dirty_enabled(pContext)) {
|
|
drgui_end_dirty(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_auto_dirty(drgui_element* pElement, drgui_rect relativeRect)
|
|
{
|
|
assert(pElement != NULL);
|
|
assert(pElement->pContext != NULL);
|
|
|
|
if (drgui_is_auto_dirty_enabled(pElement->pContext)) {
|
|
drgui_dirty(pElement, relativeRect);
|
|
}
|
|
}
|
|
|
|
|
|
void drgui__change_cursor(drgui_element* pElement, drgui_cursor_type cursor)
|
|
{
|
|
if (pElement == NULL || pElement->pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
pElement->pContext->currentCursor = cursor;
|
|
|
|
if (pElement->pContext->onChangeCursor) {
|
|
pElement->pContext->onChangeCursor(pElement, cursor);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void drgui_apply_offset_to_children_recursive(drgui_element* pParentElement, float offsetX, float offsetY)
|
|
{
|
|
assert(pParentElement != NULL);
|
|
|
|
for (drgui_element* pChild = pParentElement->pFirstChild; pChild != NULL; pChild = pChild->pNextSibling)
|
|
{
|
|
drgui_begin_auto_dirty(pParentElement);
|
|
{
|
|
drgui_auto_dirty(pParentElement, drgui_get_local_rect(pParentElement));
|
|
pChild->absolutePosX += offsetX;
|
|
pChild->absolutePosY += offsetY;
|
|
|
|
drgui_apply_offset_to_children_recursive(pChild, offsetX, offsetY);
|
|
}
|
|
drgui_end_auto_dirty(pParentElement);
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_post_on_mouse_leave_recursive(drgui_context* pContext, drgui_element* pNewElementUnderMouse, drgui_element* pOldElementUnderMouse)
|
|
{
|
|
(void)pContext;
|
|
|
|
drgui_element* pOldAncestor = pOldElementUnderMouse;
|
|
while (pOldAncestor != NULL)
|
|
{
|
|
bool isOldElementUnderMouse = pNewElementUnderMouse == pOldAncestor || drgui_is_ancestor(pOldAncestor, pNewElementUnderMouse);
|
|
if (!isOldElementUnderMouse)
|
|
{
|
|
drgui_post_outbound_event_mouse_leave(pOldAncestor);
|
|
}
|
|
|
|
pOldAncestor = pOldAncestor->pParent;
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_post_on_mouse_enter_recursive(drgui_context* pContext, drgui_element* pNewElementUnderMouse, drgui_element* pOldElementUnderMouse)
|
|
{
|
|
if (pNewElementUnderMouse == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
if (pNewElementUnderMouse->pParent != NULL) {
|
|
drgui_post_on_mouse_enter_recursive(pContext, pNewElementUnderMouse->pParent, pOldElementUnderMouse);
|
|
}
|
|
|
|
bool wasNewElementUnderMouse = pOldElementUnderMouse == pNewElementUnderMouse || drgui_is_ancestor(pNewElementUnderMouse, pOldElementUnderMouse);
|
|
if (!wasNewElementUnderMouse)
|
|
{
|
|
drgui_post_outbound_event_mouse_enter(pNewElementUnderMouse);
|
|
}
|
|
}
|
|
|
|
void drgui_update_mouse_enter_and_leave_state(drgui_context* pContext, drgui_element* pNewElementUnderMouse)
|
|
{
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_element* pOldElementUnderMouse = pContext->pElementUnderMouse;
|
|
if (pOldElementUnderMouse != pNewElementUnderMouse)
|
|
{
|
|
// We don't change the enter and leave state if an element is capturing the mouse.
|
|
if (pContext->pElementWithMouseCapture == NULL)
|
|
{
|
|
pContext->pElementUnderMouse = pNewElementUnderMouse;
|
|
|
|
drgui_cursor_type newCursor = drgui_cursor_default;
|
|
if (pNewElementUnderMouse != NULL) {
|
|
newCursor = pNewElementUnderMouse->cursor;
|
|
}
|
|
|
|
|
|
// It's intuitive to check that the new cursor is different to the old one before trying to change it, but that is not actually
|
|
// what we want to do. We'll let the event handler manage it themselves because it's possible the window manager might do some
|
|
// window-specific cursor management and the old and new elements are on different windows.
|
|
drgui__change_cursor(pNewElementUnderMouse, newCursor);
|
|
|
|
|
|
|
|
// The the event handlers below, remember that ancestors are considered hovered if a descendant is the element under the mouse.
|
|
|
|
// on_mouse_leave
|
|
drgui_post_on_mouse_leave_recursive(pContext, pNewElementUnderMouse, pOldElementUnderMouse);
|
|
|
|
// on_mouse_enter
|
|
drgui_post_on_mouse_enter_recursive(pContext, pNewElementUnderMouse, pOldElementUnderMouse);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_post_outbound_event_move(drgui_element* pElement, float newRelativePosX, float newRelativePosY)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onMove) {
|
|
pElement->onMove(pElement, newRelativePosX, newRelativePosY);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_size(drgui_element* pElement, float newWidth, float newHeight)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onSize) {
|
|
pElement->onSize(pElement, newWidth, newHeight);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_mouse_enter(drgui_element* pElement)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onMouseEnter) {
|
|
pElement->onMouseEnter(pElement);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_mouse_leave(drgui_element* pElement)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onMouseLeave) {
|
|
pElement->onMouseLeave(pElement);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_mouse_move(drgui_element* pElement, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onMouseMove) {
|
|
pElement->onMouseMove(pElement, relativeMousePosX, relativeMousePosY, stateFlags);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_mouse_button_down(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onMouseButtonDown) {
|
|
pElement->onMouseButtonDown(pElement, mouseButton, relativeMousePosX, relativeMousePosY, stateFlags);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_mouse_button_up(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onMouseButtonUp) {
|
|
pElement->onMouseButtonUp(pElement, mouseButton, relativeMousePosX, relativeMousePosY, stateFlags);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_mouse_button_dblclick(drgui_element* pElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onMouseButtonDblClick) {
|
|
pElement->onMouseButtonDblClick(pElement, mouseButton, relativeMousePosX, relativeMousePosY, stateFlags);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_mouse_wheel(drgui_element* pElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onMouseWheel) {
|
|
pElement->onMouseWheel(pElement, delta, relativeMousePosX, relativeMousePosY, stateFlags);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_key_down(drgui_element* pElement, drgui_key key, int stateFlags)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onKeyDown) {
|
|
pElement->onKeyDown(pElement, key, stateFlags);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_key_up(drgui_element* pElement, drgui_key key, int stateFlags)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onKeyUp) {
|
|
pElement->onKeyUp(pElement, key, stateFlags);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_printable_key_down(drgui_element* pElement, unsigned int character, int stateFlags)
|
|
{
|
|
if (drgui_begin_outbound_event(pElement))
|
|
{
|
|
if (pElement->onPrintableKeyDown) {
|
|
pElement->onPrintableKeyDown(pElement, character, stateFlags);
|
|
}
|
|
|
|
drgui_end_outbound_event(pElement);
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_post_outbound_event_dirty(drgui_element* pElement, drgui_rect relativeRect)
|
|
{
|
|
if (pElement != NULL)
|
|
{
|
|
if (pElement->onDirty) {
|
|
pElement->onDirty(pElement, relativeRect);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_dirty_global(drgui_element* pElement, drgui_rect relativeRect)
|
|
{
|
|
if (pElement != NULL && pElement->pContext != NULL)
|
|
{
|
|
if (pElement->pContext->onGlobalDirty) {
|
|
pElement->pContext->onGlobalDirty(pElement, relativeRect);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_capture_mouse(drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL)
|
|
{
|
|
if (pElement->onCaptureMouse) {
|
|
pElement->onCaptureMouse(pElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_capture_mouse_global(drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL && pElement->pContext != NULL)
|
|
{
|
|
if (pElement->pContext->onGlobalCaptureMouse) {
|
|
pElement->pContext->onGlobalCaptureMouse(pElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_release_mouse(drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL)
|
|
{
|
|
if (pElement->onReleaseMouse) {
|
|
pElement->onReleaseMouse(pElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_release_mouse_global(drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL && pElement->pContext != NULL)
|
|
{
|
|
if (pElement->pContext->onGlobalReleaseMouse) {
|
|
pElement->pContext->onGlobalReleaseMouse(pElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_post_outbound_event_capture_keyboard(drgui_element* pElement, drgui_element* pPrevCapturedElement)
|
|
{
|
|
if (pElement != NULL)
|
|
{
|
|
if (pElement->onCaptureKeyboard) {
|
|
pElement->onCaptureKeyboard(pElement, pPrevCapturedElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_capture_keyboard_global(drgui_element* pElement, drgui_element* pPrevCapturedElement)
|
|
{
|
|
if (pElement != NULL && pElement->pContext != NULL)
|
|
{
|
|
if (pElement->pContext->onGlobalCaptureKeyboard) {
|
|
pElement->pContext->onGlobalCaptureKeyboard(pElement, pPrevCapturedElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_release_keyboard(drgui_element* pElement, drgui_element* pNewCapturedElement)
|
|
{
|
|
if (pElement != NULL)
|
|
{
|
|
if (pElement->onReleaseKeyboard) {
|
|
pElement->onReleaseKeyboard(pElement, pNewCapturedElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_post_outbound_event_release_keyboard_global(drgui_element* pElement, drgui_element* pNewCapturedElement)
|
|
{
|
|
if (pElement != NULL && pElement->pContext != NULL)
|
|
{
|
|
if (pElement->pContext->onGlobalReleaseKeyboard) {
|
|
pElement->pContext->onGlobalReleaseKeyboard(pElement, pNewCapturedElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_log(drgui_context* pContext, const char* message)
|
|
{
|
|
if (pContext != NULL)
|
|
{
|
|
if (pContext->onLog) {
|
|
pContext->onLog(pContext, message);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// CORE API
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
drgui_context* drgui_create_context()
|
|
{
|
|
drgui_context* pContext = (drgui_context*)calloc(1, sizeof(drgui_context));
|
|
if (pContext != NULL) {
|
|
pContext->currentCursor = drgui_cursor_default;
|
|
}
|
|
|
|
return pContext;
|
|
}
|
|
|
|
void drgui_delete_context(drgui_context* pContext)
|
|
{
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
// Make sure the mouse capture is released.
|
|
if (pContext->pElementWithMouseCapture != NULL)
|
|
{
|
|
drgui_log(pContext, "WARNING: Deleting the GUI context while an element still has the mouse capture.");
|
|
drgui_release_mouse(pContext);
|
|
}
|
|
|
|
// Make sure the keyboard capture is released.
|
|
if (pContext->pElementWithKeyboardCapture != NULL)
|
|
{
|
|
drgui_log(pContext, "WARNING: Deleting the GUI context while an element still has the keyboard capture.");
|
|
drgui_release_keyboard(pContext);
|
|
}
|
|
|
|
|
|
if (drgui_is_handling_inbound_event(pContext))
|
|
{
|
|
// An inbound event is still being processed - we don't want to delete the context straight away because we can't
|
|
// trust external event handlers to not try to access the context later on. To do this we just set the flag that
|
|
// the context is deleted. It will then be deleted for real at the end of the inbound event handler.
|
|
drgui_mark_context_as_dead(pContext);
|
|
}
|
|
else
|
|
{
|
|
// An inbound event is not being processed, so delete the context straight away.
|
|
drgui_delete_context_for_real(pContext);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// Events
|
|
|
|
void drgui_post_inbound_event_mouse_leave(drgui_element* pTopLevelElement)
|
|
{
|
|
if (pTopLevelElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_context* pContext = pTopLevelElement->pContext;
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_begin_inbound_event(pContext);
|
|
{
|
|
// We assume that was previously under the mouse was either pTopLevelElement itself or one of it's descendants.
|
|
drgui_update_mouse_enter_and_leave_state(pContext, NULL);
|
|
}
|
|
drgui_end_inbound_event(pContext);
|
|
}
|
|
|
|
void drgui_post_inbound_event_mouse_move(drgui_element* pTopLevelElement, int mousePosX, int mousePosY, int stateFlags)
|
|
{
|
|
if (pTopLevelElement == NULL || pTopLevelElement->pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
drgui_begin_inbound_event(pTopLevelElement->pContext);
|
|
{
|
|
/// A pointer to the top level element that was passed in from the last inbound mouse move event.
|
|
pTopLevelElement->pContext->pLastMouseMoveTopLevelElement = pTopLevelElement;
|
|
|
|
/// The position of the mouse that was passed in from the last inbound mouse move event.
|
|
pTopLevelElement->pContext->lastMouseMovePosX = (float)mousePosX;
|
|
pTopLevelElement->pContext->lastMouseMovePosY = (float)mousePosY;
|
|
|
|
|
|
|
|
// The first thing we need to do is find the new element that's sitting under the mouse.
|
|
drgui_element* pNewElementUnderMouse = drgui_find_element_under_point(pTopLevelElement, (float)mousePosX, (float)mousePosY);
|
|
|
|
// Now that we know which element is sitting under the mouse we need to check if the mouse has entered into a new element.
|
|
drgui_update_mouse_enter_and_leave_state(pTopLevelElement->pContext, pNewElementUnderMouse);
|
|
|
|
|
|
drgui_element* pEventReceiver = pTopLevelElement->pContext->pElementWithMouseCapture;
|
|
if (pEventReceiver == NULL)
|
|
{
|
|
pEventReceiver = pNewElementUnderMouse;
|
|
}
|
|
|
|
if (pEventReceiver != NULL)
|
|
{
|
|
float relativeMousePosX = (float)mousePosX;
|
|
float relativeMousePosY = (float)mousePosY;
|
|
drgui_make_point_relative(pEventReceiver, &relativeMousePosX, &relativeMousePosY);
|
|
|
|
drgui_post_outbound_event_mouse_move(pEventReceiver, (int)relativeMousePosX, (int)relativeMousePosY, stateFlags);
|
|
}
|
|
}
|
|
drgui_end_inbound_event(pTopLevelElement->pContext);
|
|
}
|
|
|
|
void drgui_post_inbound_event_mouse_button_down(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags)
|
|
{
|
|
if (pTopLevelElement == NULL || pTopLevelElement->pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_context* pContext = pTopLevelElement->pContext;
|
|
drgui_begin_inbound_event(pContext);
|
|
{
|
|
drgui_element* pEventReceiver = pContext->pElementWithMouseCapture;
|
|
if (pEventReceiver == NULL)
|
|
{
|
|
pEventReceiver = pContext->pElementUnderMouse;
|
|
|
|
if (pEventReceiver == NULL)
|
|
{
|
|
// We'll get here if this message is posted without a prior mouse move event.
|
|
pEventReceiver = drgui_find_element_under_point(pTopLevelElement, (float)mousePosX, (float)mousePosY);
|
|
}
|
|
}
|
|
|
|
|
|
if (pEventReceiver != NULL)
|
|
{
|
|
float relativeMousePosX = (float)mousePosX;
|
|
float relativeMousePosY = (float)mousePosY;
|
|
drgui_make_point_relative(pEventReceiver, &relativeMousePosX, &relativeMousePosY);
|
|
|
|
drgui_post_outbound_event_mouse_button_down(pEventReceiver, mouseButton, (int)relativeMousePosX, (int)relativeMousePosY, stateFlags);
|
|
}
|
|
}
|
|
drgui_end_inbound_event(pContext);
|
|
}
|
|
|
|
void drgui_post_inbound_event_mouse_button_up(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags)
|
|
{
|
|
if (pTopLevelElement == NULL || pTopLevelElement->pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_context* pContext = pTopLevelElement->pContext;
|
|
drgui_begin_inbound_event(pContext);
|
|
{
|
|
drgui_element* pEventReceiver = pContext->pElementWithMouseCapture;
|
|
if (pEventReceiver == NULL)
|
|
{
|
|
pEventReceiver = pContext->pElementUnderMouse;
|
|
|
|
if (pEventReceiver == NULL)
|
|
{
|
|
// We'll get here if this message is posted without a prior mouse move event.
|
|
pEventReceiver = drgui_find_element_under_point(pTopLevelElement, (float)mousePosX, (float)mousePosY);
|
|
}
|
|
}
|
|
|
|
|
|
if (pEventReceiver != NULL)
|
|
{
|
|
float relativeMousePosX = (float)mousePosX;
|
|
float relativeMousePosY = (float)mousePosY;
|
|
drgui_make_point_relative(pEventReceiver, &relativeMousePosX, &relativeMousePosY);
|
|
|
|
drgui_post_outbound_event_mouse_button_up(pEventReceiver, mouseButton, (int)relativeMousePosX, (int)relativeMousePosY, stateFlags);
|
|
}
|
|
}
|
|
drgui_end_inbound_event(pContext);
|
|
}
|
|
|
|
void drgui_post_inbound_event_mouse_button_dblclick(drgui_element* pTopLevelElement, int mouseButton, int mousePosX, int mousePosY, int stateFlags)
|
|
{
|
|
if (pTopLevelElement == NULL || pTopLevelElement->pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_context* pContext = pTopLevelElement->pContext;
|
|
drgui_begin_inbound_event(pContext);
|
|
{
|
|
drgui_element* pEventReceiver = pContext->pElementWithMouseCapture;
|
|
if (pEventReceiver == NULL)
|
|
{
|
|
pEventReceiver = pContext->pElementUnderMouse;
|
|
|
|
if (pEventReceiver == NULL)
|
|
{
|
|
// We'll get here if this message is posted without a prior mouse move event.
|
|
pEventReceiver = drgui_find_element_under_point(pTopLevelElement, (float)mousePosX, (float)mousePosY);
|
|
}
|
|
}
|
|
|
|
|
|
if (pEventReceiver != NULL)
|
|
{
|
|
float relativeMousePosX = (float)mousePosX;
|
|
float relativeMousePosY = (float)mousePosY;
|
|
drgui_make_point_relative(pEventReceiver, &relativeMousePosX, &relativeMousePosY);
|
|
|
|
drgui_post_outbound_event_mouse_button_dblclick(pEventReceiver, mouseButton, (int)relativeMousePosX, (int)relativeMousePosY, stateFlags);
|
|
}
|
|
}
|
|
drgui_end_inbound_event(pContext);
|
|
}
|
|
|
|
void drgui_post_inbound_event_mouse_wheel(drgui_element* pTopLevelElement, int delta, int mousePosX, int mousePosY, int stateFlags)
|
|
{
|
|
if (pTopLevelElement == NULL || pTopLevelElement->pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_context* pContext = pTopLevelElement->pContext;
|
|
drgui_begin_inbound_event(pContext);
|
|
{
|
|
drgui_element* pEventReceiver = pContext->pElementWithMouseCapture;
|
|
if (pEventReceiver == NULL)
|
|
{
|
|
pEventReceiver = pContext->pElementUnderMouse;
|
|
|
|
if (pEventReceiver == NULL)
|
|
{
|
|
// We'll get here if this message is posted without a prior mouse move event.
|
|
pEventReceiver = drgui_find_element_under_point(pTopLevelElement, (float)mousePosX, (float)mousePosY);
|
|
}
|
|
}
|
|
|
|
|
|
if (pEventReceiver != NULL)
|
|
{
|
|
float relativeMousePosX = (float)mousePosX;
|
|
float relativeMousePosY = (float)mousePosY;
|
|
drgui_make_point_relative(pEventReceiver, &relativeMousePosX, &relativeMousePosY);
|
|
|
|
drgui_post_outbound_event_mouse_wheel(pEventReceiver, delta, (int)relativeMousePosX, (int)relativeMousePosY, stateFlags);
|
|
}
|
|
}
|
|
drgui_end_inbound_event(pContext);
|
|
}
|
|
|
|
void drgui_post_inbound_event_key_down(drgui_context* pContext, drgui_key key, int stateFlags)
|
|
{
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_begin_inbound_event(pContext);
|
|
{
|
|
if (pContext->pElementWithKeyboardCapture != NULL) {
|
|
drgui_post_outbound_event_key_down(pContext->pElementWithKeyboardCapture, key, stateFlags);
|
|
}
|
|
}
|
|
drgui_end_inbound_event(pContext);
|
|
}
|
|
|
|
void drgui_post_inbound_event_key_up(drgui_context* pContext, drgui_key key, int stateFlags)
|
|
{
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_begin_inbound_event(pContext);
|
|
{
|
|
if (pContext->pElementWithKeyboardCapture != NULL) {
|
|
drgui_post_outbound_event_key_up(pContext->pElementWithKeyboardCapture, key, stateFlags);
|
|
}
|
|
}
|
|
drgui_end_inbound_event(pContext);
|
|
}
|
|
|
|
void drgui_post_inbound_event_printable_key_down(drgui_context* pContext, unsigned int character, int stateFlags)
|
|
{
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_begin_inbound_event(pContext);
|
|
{
|
|
if (pContext->pElementWithKeyboardCapture != NULL) {
|
|
drgui_post_outbound_event_printable_key_down(pContext->pElementWithKeyboardCapture, character, stateFlags);
|
|
}
|
|
}
|
|
drgui_end_inbound_event(pContext);
|
|
}
|
|
|
|
|
|
|
|
void drgui_set_global_on_dirty(drgui_context * pContext, drgui_on_dirty_proc onDirty)
|
|
{
|
|
if (pContext != NULL) {
|
|
pContext->onGlobalDirty = onDirty;
|
|
}
|
|
}
|
|
|
|
void drgui_set_global_on_capture_mouse(drgui_context* pContext, drgui_on_capture_mouse_proc onCaptureMouse)
|
|
{
|
|
if (pContext != NULL) {
|
|
pContext->onGlobalCaptureMouse = onCaptureMouse;
|
|
}
|
|
}
|
|
|
|
void drgui_set_global_on_release_mouse(drgui_context* pContext, drgui_on_release_mouse_proc onReleaseMouse)
|
|
{
|
|
if (pContext != NULL) {
|
|
pContext->onGlobalReleaseMouse = onReleaseMouse;
|
|
}
|
|
}
|
|
|
|
void drgui_set_global_on_capture_keyboard(drgui_context* pContext, drgui_on_capture_keyboard_proc onCaptureKeyboard)
|
|
{
|
|
if (pContext != NULL) {
|
|
pContext->onGlobalCaptureKeyboard = onCaptureKeyboard;
|
|
}
|
|
}
|
|
|
|
void drgui_set_global_on_release_keyboard(drgui_context* pContext, drgui_on_capture_keyboard_proc onReleaseKeyboard)
|
|
{
|
|
if (pContext != NULL) {
|
|
pContext->onGlobalReleaseKeyboard = onReleaseKeyboard;
|
|
}
|
|
}
|
|
|
|
void drgui_set_global_on_change_cursor(drgui_context* pContext, drgui_on_change_cursor_proc onChangeCursor)
|
|
{
|
|
if (pContext != NULL) {
|
|
pContext->onChangeCursor = onChangeCursor;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_delete_element(drgui_context* pContext, drgui_on_delete_element_proc onDeleteElement)
|
|
{
|
|
if (pContext != NULL) {
|
|
pContext->onDeleteElement = onDeleteElement;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_log(drgui_context* pContext, drgui_on_log onLog)
|
|
{
|
|
if (pContext != NULL) {
|
|
pContext->onLog = onLog;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// Elements
|
|
|
|
drgui_element* drgui_create_element(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData)
|
|
{
|
|
if (pContext != NULL)
|
|
{
|
|
drgui_element* pElement = (drgui_element*)calloc(1, sizeof(drgui_element) + extraDataSize);
|
|
if (pElement != NULL) {
|
|
pElement->pContext = pContext;
|
|
pElement->pParent = pParent;
|
|
pElement->cursor = drgui_cursor_default;
|
|
pElement->dirtyRect = drgui_make_inside_out_rect();
|
|
|
|
pElement->extraDataSize = extraDataSize;
|
|
if (pExtraData != NULL) {
|
|
memcpy(pElement->pExtraData, pExtraData, extraDataSize);
|
|
}
|
|
|
|
// Add to the the hierarchy.
|
|
drgui_append_without_detach_or_redraw(pElement, pElement->pParent);
|
|
|
|
|
|
// Have the element positioned at 0,0 relative to the parent by default.
|
|
if (pParent != NULL) {
|
|
pElement->absolutePosX = pParent->absolutePosX;
|
|
pElement->absolutePosY = pParent->absolutePosY;
|
|
}
|
|
|
|
|
|
return pElement;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void drgui_delete_element(drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_context* pContext = pElement->pContext;
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (drgui_is_element_marked_as_dead(pElement)) {
|
|
drgui_log(pContext, "WARNING: Attempting to delete an element that is already marked for deletion.");
|
|
return;
|
|
}
|
|
|
|
drgui_mark_element_as_dead(pElement);
|
|
|
|
|
|
// Notify the application that the element is being deleted. Do this at the top so the event handler can access things like the hierarchy and
|
|
// whatnot in case it needs it.
|
|
if (pContext->onDeleteElement) {
|
|
pContext->onDeleteElement(pElement);
|
|
}
|
|
|
|
|
|
// If this was element is marked as the one that was last under the mouse it needs to be unset.
|
|
bool needsMouseUpdate = false;
|
|
if (pContext->pElementUnderMouse == pElement)
|
|
{
|
|
pContext->pElementUnderMouse = NULL;
|
|
needsMouseUpdate = true;
|
|
}
|
|
|
|
if (pContext->pLastMouseMoveTopLevelElement == pElement)
|
|
{
|
|
pContext->pLastMouseMoveTopLevelElement = NULL;
|
|
pContext->lastMouseMovePosX = 0;
|
|
pContext->lastMouseMovePosY = 0;
|
|
needsMouseUpdate = false; // It was a top-level element so the mouse enter/leave state doesn't need an update.
|
|
}
|
|
|
|
|
|
// If this element has the mouse capture it needs to be released.
|
|
if (pContext->pElementWithMouseCapture == pElement)
|
|
{
|
|
drgui_log(pContext, "WARNING: Deleting an element while it still has the mouse capture.");
|
|
drgui_release_mouse(pContext);
|
|
}
|
|
|
|
// If this element has the keyboard capture it needs to be released.
|
|
if (pContext->pElementWithKeyboardCapture == pElement)
|
|
{
|
|
drgui_log(pContext, "WARNING: Deleting an element while it still has the keyboard capture.");
|
|
drgui_release_keyboard(pContext);
|
|
}
|
|
|
|
// Is this element in the middle of being marked as dirty?
|
|
for (size_t iDirtyElement = 0; iDirtyElement < pContext->dirtyElementCount; ++iDirtyElement) {
|
|
if (pContext->ppDirtyElements[iDirtyElement] == pElement) {
|
|
drgui_log(pContext, "WARNING: Deleting an element while it is being marked as dirty.");
|
|
for (size_t iDirtyElement2 = iDirtyElement; iDirtyElement2+1 < pContext->dirtyElementCount; ++iDirtyElement2) {
|
|
pContext->ppDirtyElements[iDirtyElement2] = pContext->ppDirtyElements[iDirtyElement2+1];
|
|
}
|
|
|
|
pContext->dirtyElementCount -= 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
if (pContext->pDirtyTopLevelElement == pElement)
|
|
{
|
|
drgui_log(pContext, "WARNING: Deleting an element while it is being marked as dirty.");
|
|
pContext->pDirtyTopLevelElement = NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
// Deleting this element may have resulted in the mouse entering a new element. Here is where we do a mouse enter/leave update.
|
|
if (needsMouseUpdate)
|
|
{
|
|
pElement->onHitTest = drgui_pass_through_hit_test; // <-- This ensures we don't include this element when searching for the new element under the mouse.
|
|
drgui_update_mouse_enter_and_leave_state(pContext, drgui_find_element_under_point(pContext->pLastMouseMoveTopLevelElement, pContext->lastMouseMovePosX, pContext->lastMouseMovePosY));
|
|
}
|
|
|
|
|
|
// Here is where we need to detach the element from the hierarchy. When doing this we want to ensure the element is not redrawn when
|
|
// it's children are detached. To do this we simply detach the event handler.
|
|
pElement->onPaint = NULL;
|
|
|
|
// The parent needs to be redraw after detaching.
|
|
drgui_element* pParent = pElement->pParent;
|
|
drgui_rect relativeRect = drgui_get_relative_rect(pElement);
|
|
|
|
|
|
// Orphan the element first.
|
|
drgui_detach_without_redraw(pElement);
|
|
|
|
// Children need to be deleted before deleting the element itself.
|
|
while (pElement->pLastChild != NULL) {
|
|
drgui_delete_element(pElement->pLastChild);
|
|
}
|
|
|
|
|
|
// The parent needs to be redrawn.
|
|
if (pParent) {
|
|
drgui_dirty(pParent, relativeRect);
|
|
}
|
|
|
|
|
|
// Finally, we to decided whether or not the element should be deleted for real straight away or not. If the element is being
|
|
// deleted within an event handler it should be delayed because the event handlers may try referencing it afterwards.
|
|
if (!drgui_is_handling_inbound_event(pContext)) {
|
|
drgui_delete_element_for_real(pElement);
|
|
}
|
|
}
|
|
|
|
|
|
size_t drgui_get_extra_data_size(drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
return pElement->extraDataSize;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void* drgui_get_extra_data(drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
return pElement->pExtraData;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
bool drgui_set_type(drgui_element* pElement, const char* type)
|
|
{
|
|
if (pElement == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return drgui__strcpy_s(pElement->type, sizeof(pElement->type), (type == NULL) ? "" : type) == 0;
|
|
}
|
|
|
|
const char* drgui_get_type(drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pElement->type;
|
|
}
|
|
|
|
bool drgui_is_of_type(drgui_element* pElement, const char* type)
|
|
{
|
|
if (pElement == NULL || type == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return strncmp(pElement->type, type, strlen(type)) == 0;
|
|
}
|
|
|
|
|
|
void drgui_hide(drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->flags |= IS_ELEMENT_HIDDEN;
|
|
drgui_auto_dirty(pElement, drgui_get_local_rect(pElement));
|
|
}
|
|
}
|
|
|
|
void drgui_show(drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->flags &= ~IS_ELEMENT_HIDDEN;
|
|
drgui_auto_dirty(pElement, drgui_get_local_rect(pElement));
|
|
}
|
|
}
|
|
|
|
bool drgui_is_visible(const drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
return (pElement->flags & IS_ELEMENT_HIDDEN) == 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_is_visible_recursive(const drgui_element* pElement)
|
|
{
|
|
if (drgui_is_visible(pElement))
|
|
{
|
|
assert(pElement->pParent != NULL);
|
|
|
|
if (pElement->pParent != NULL) {
|
|
return drgui_is_visible(pElement->pParent);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void drgui_disable_clipping(drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->flags |= IS_ELEMENT_CLIPPING_DISABLED;
|
|
}
|
|
}
|
|
|
|
void drgui_enable_clipping(drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->flags &= ~IS_ELEMENT_CLIPPING_DISABLED;
|
|
}
|
|
}
|
|
|
|
bool drgui_is_clipping_enabled(const drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
return (pElement->flags & IS_ELEMENT_CLIPPING_DISABLED) == 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void drgui_capture_mouse(drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pElement->pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
if (pElement->pContext->pElementWithMouseCapture != pElement)
|
|
{
|
|
// Release the previous capture first.
|
|
if (pElement->pContext->pElementWithMouseCapture != NULL) {
|
|
drgui_release_mouse(pElement->pContext);
|
|
}
|
|
|
|
assert(pElement->pContext->pElementWithMouseCapture == NULL);
|
|
|
|
pElement->pContext->pElementWithMouseCapture = pElement;
|
|
|
|
// Two events need to be posted - the global on_capture_mouse event and the local on_capture_mouse event.
|
|
drgui_post_outbound_event_capture_mouse(pElement);
|
|
|
|
if (pElement == pElement->pContext->pElementWithMouseCapture) { // <-- Only post the global event handler if the element still has the capture.
|
|
drgui_post_outbound_event_capture_mouse_global(pElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_release_mouse(drgui_context* pContext)
|
|
{
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
// Events need to be posted before setting the internal pointer.
|
|
if (!drgui_is_element_marked_as_dead(pContext->pElementWithMouseCapture)) { // <-- There's a chace the element is releaseing the keyboard due to being deleted. Don't want to post an event in this case.
|
|
drgui_post_outbound_event_release_mouse(pContext->pElementWithMouseCapture);
|
|
drgui_post_outbound_event_release_mouse_global(pContext->pElementWithMouseCapture);
|
|
}
|
|
|
|
// We want to set the internal pointer to NULL after posting the events since that is when it has truly released the mouse.
|
|
pContext->pElementWithMouseCapture = NULL;
|
|
|
|
|
|
// After releasing the mouse the cursor may be sitting on top of a different element - we want to recheck that.
|
|
drgui_update_mouse_enter_and_leave_state(pContext, drgui_find_element_under_point(pContext->pLastMouseMoveTopLevelElement, pContext->lastMouseMovePosX, pContext->lastMouseMovePosY));
|
|
}
|
|
|
|
void drgui_release_mouse_no_global_notify(drgui_context* pContext)
|
|
{
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_on_release_mouse_proc prevProc = pContext->onGlobalReleaseMouse;
|
|
pContext->onGlobalReleaseMouse = NULL;
|
|
drgui_release_mouse(pContext);
|
|
pContext->onGlobalReleaseMouse = prevProc;
|
|
}
|
|
|
|
drgui_element* drgui_get_element_with_mouse_capture(drgui_context* pContext)
|
|
{
|
|
if (pContext == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pContext->pElementWithMouseCapture;
|
|
}
|
|
|
|
bool drgui_has_mouse_capture(drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_get_element_with_mouse_capture(pElement->pContext) == pElement;
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE void drgui_release_keyboard_private(drgui_context* pContext, drgui_element* pNewCapturedElement)
|
|
{
|
|
assert(pContext != NULL);
|
|
|
|
// It is reasonable to expect that an application will want to change keyboard focus from within the release_keyboard
|
|
// event handler. The problem with this is that is can cause a infinite dependency chain. We need to handle that case
|
|
// by setting a flag that keeps track of whether or not we are in the middle of a release_keyboard event. At the end
|
|
// we look at the element that want's the keyboard focuse and explicitly capture it at the end.
|
|
|
|
pContext->flags |= IS_RELEASING_KEYBOARD;
|
|
{
|
|
drgui_element* pPrevCapturedElement = pContext->pElementWithKeyboardCapture;
|
|
pContext->pElementWithKeyboardCapture = NULL;
|
|
|
|
if (!drgui_is_element_marked_as_dead(pPrevCapturedElement)) { // <-- There's a chace the element is releaseing the keyboard due to being deleted. Don't want to post an event in this case.
|
|
drgui_post_outbound_event_release_keyboard(pPrevCapturedElement, pNewCapturedElement);
|
|
drgui_post_outbound_event_release_keyboard_global(pPrevCapturedElement, pNewCapturedElement);
|
|
}
|
|
}
|
|
pContext->flags &= ~IS_RELEASING_KEYBOARD;
|
|
|
|
// Explicitly capture the keyboard.
|
|
drgui_capture_keyboard(pContext->pElementWantingKeyboardCapture);
|
|
pContext->pElementWantingKeyboardCapture = NULL;
|
|
}
|
|
|
|
void drgui_capture_keyboard(drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pElement->pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
if ((pElement->pContext->flags & IS_RELEASING_KEYBOARD) != 0) {
|
|
pElement->pContext->pElementWantingKeyboardCapture = pElement;
|
|
return;
|
|
}
|
|
|
|
|
|
if (pElement->pContext->pElementWithKeyboardCapture != pElement)
|
|
{
|
|
// Release the previous capture first.
|
|
drgui_element* pPrevElementWithKeyboardCapture = pElement->pContext->pElementWithKeyboardCapture;
|
|
if (pPrevElementWithKeyboardCapture != NULL) {
|
|
drgui_release_keyboard_private(pElement->pContext, pElement);
|
|
}
|
|
|
|
assert(pElement->pContext->pElementWithKeyboardCapture == NULL);
|
|
|
|
pElement->pContext->pElementWithKeyboardCapture = pElement;
|
|
pElement->pContext->pElementWantingKeyboardCapture = NULL;
|
|
|
|
// Two events need to be posted - the global on_capture event and the local on_capture event. The problem, however, is that the
|
|
// local event handler may change the keyboard capture internally, such as if it wants to pass it's focus onto an internal child
|
|
// element or whatnot. In this case we don't want to fire the global event handler because it will result in superfluous event
|
|
// posting, and could also be posted with an incorrect element.
|
|
drgui_post_outbound_event_capture_keyboard(pElement, pPrevElementWithKeyboardCapture);
|
|
|
|
if (pElement == pElement->pContext->pElementWithKeyboardCapture) { // <-- Only post the global event handler if the element still has the capture.
|
|
drgui_post_outbound_event_capture_keyboard_global(pElement, pPrevElementWithKeyboardCapture);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_release_keyboard(drgui_context* pContext)
|
|
{
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_release_keyboard_private(pContext, NULL);
|
|
}
|
|
|
|
void drgui_release_keyboard_no_global_notify(drgui_context* pContext)
|
|
{
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_on_release_keyboard_proc prevProc = pContext->onGlobalReleaseKeyboard;
|
|
pContext->onGlobalReleaseKeyboard = NULL;
|
|
drgui_release_keyboard(pContext);
|
|
pContext->onGlobalReleaseKeyboard = prevProc;
|
|
}
|
|
|
|
drgui_element* drgui_get_element_with_keyboard_capture(drgui_context* pContext)
|
|
{
|
|
if (pContext == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pContext->pElementWithKeyboardCapture;
|
|
}
|
|
|
|
bool drgui_has_keyboard_capture(drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_get_element_with_keyboard_capture(pElement->pContext) == pElement;
|
|
}
|
|
|
|
|
|
void drgui_set_cursor(drgui_element* pElement, drgui_cursor_type cursor)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
pElement->cursor = cursor;
|
|
|
|
if (drgui_is_element_under_mouse(pElement) && pElement->pContext->currentCursor != cursor) {
|
|
drgui__change_cursor(pElement, cursor);
|
|
}
|
|
}
|
|
|
|
drgui_cursor_type drgui_get_cursor(drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return drgui_cursor_none;
|
|
}
|
|
|
|
return pElement->cursor;
|
|
}
|
|
|
|
|
|
|
|
//// Events ////
|
|
|
|
void drgui_set_on_move(drgui_element * pElement, drgui_on_move_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onMove = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_size(drgui_element * pElement, drgui_on_size_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onSize = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_mouse_enter(drgui_element* pElement, drgui_on_mouse_enter_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onMouseEnter = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_mouse_leave(drgui_element* pElement, drgui_on_mouse_leave_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onMouseLeave = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_mouse_move(drgui_element* pElement, drgui_on_mouse_move_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onMouseMove = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_mouse_button_down(drgui_element* pElement, drgui_on_mouse_button_down_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onMouseButtonDown = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_mouse_button_up(drgui_element* pElement, drgui_on_mouse_button_up_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onMouseButtonUp = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_mouse_button_dblclick(drgui_element* pElement, drgui_on_mouse_button_dblclick_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onMouseButtonDblClick = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_mouse_wheel(drgui_element* pElement, drgui_on_mouse_wheel_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onMouseWheel = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_key_down(drgui_element* pElement, drgui_on_key_down_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onKeyDown = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_key_up(drgui_element* pElement, drgui_on_key_up_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onKeyUp = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_printable_key_down(drgui_element* pElement, drgui_on_printable_key_down_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onPrintableKeyDown = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_paint(drgui_element* pElement, drgui_on_paint_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onPaint = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_dirty(drgui_element * pElement, drgui_on_dirty_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onDirty = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_hittest(drgui_element* pElement, drgui_on_hittest_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onHitTest = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_capture_mouse(drgui_element* pElement, drgui_on_capture_mouse_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onCaptureMouse = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_release_mouse(drgui_element* pElement, drgui_on_release_mouse_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onReleaseMouse = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_capture_keyboard(drgui_element* pElement, drgui_on_capture_keyboard_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onCaptureKeyboard = callback;
|
|
}
|
|
}
|
|
|
|
void drgui_set_on_release_keyboard(drgui_element* pElement, drgui_on_release_keyboard_proc callback)
|
|
{
|
|
if (pElement != NULL) {
|
|
pElement->onReleaseKeyboard = callback;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool drgui_is_point_inside_element_bounds(const drgui_element* pElement, float absolutePosX, float absolutePosY)
|
|
{
|
|
if (absolutePosX < pElement->absolutePosX ||
|
|
absolutePosX < pElement->absolutePosY)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (absolutePosX >= pElement->absolutePosX + pElement->width ||
|
|
absolutePosY >= pElement->absolutePosY + pElement->height)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool drgui_is_point_inside_element(drgui_element* pElement, float absolutePosX, float absolutePosY)
|
|
{
|
|
if (drgui_is_point_inside_element_bounds(pElement, absolutePosX, absolutePosY))
|
|
{
|
|
// It is valid for onHitTest to be null, in which case we use the default hit test which assumes the element is just a rectangle
|
|
// equal to the size of it's bounds. It's equivalent to onHitTest always returning true.
|
|
|
|
if (pElement->onHitTest) {
|
|
return pElement->onHitTest(pElement, absolutePosX - pElement->absolutePosX, absolutePosY - pElement->absolutePosY);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
typedef struct
|
|
{
|
|
drgui_element* pElementUnderPoint;
|
|
float absolutePosX;
|
|
float absolutePosY;
|
|
}drgui_find_element_under_point_data;
|
|
|
|
bool drgui_find_element_under_point_iterator(drgui_element* pElement, drgui_rect* pRelativeVisibleRect, void* pUserData)
|
|
{
|
|
assert(pElement != NULL);
|
|
assert(pRelativeVisibleRect != NULL);
|
|
|
|
drgui_find_element_under_point_data* pData = (drgui_find_element_under_point_data*)pUserData;
|
|
assert(pData != NULL);
|
|
|
|
float relativePosX = pData->absolutePosX;
|
|
float relativePosY = pData->absolutePosY;
|
|
drgui_make_point_relative(pElement, &relativePosX, &relativePosY);
|
|
|
|
if (drgui_rect_contains_point(*pRelativeVisibleRect, relativePosX, relativePosY))
|
|
{
|
|
if (pElement->onHitTest) {
|
|
if (pElement->onHitTest(pElement, relativePosX, relativePosY)) {
|
|
pData->pElementUnderPoint = pElement;
|
|
}
|
|
} else {
|
|
pData->pElementUnderPoint = pElement;
|
|
}
|
|
}
|
|
|
|
|
|
// Always return true to ensure the entire hierarchy is checked.
|
|
return true;
|
|
}
|
|
|
|
drgui_element* drgui_find_element_under_point(drgui_element* pTopLevelElement, float absolutePosX, float absolutePosY)
|
|
{
|
|
if (pTopLevelElement == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_find_element_under_point_data data;
|
|
data.pElementUnderPoint = NULL;
|
|
data.absolutePosX = absolutePosX;
|
|
data.absolutePosY = absolutePosY;
|
|
drgui_iterate_visible_elements(pTopLevelElement, drgui_get_absolute_rect(pTopLevelElement), drgui_find_element_under_point_iterator, &data);
|
|
|
|
return data.pElementUnderPoint;
|
|
}
|
|
|
|
bool drgui_is_element_under_mouse(drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_find_element_under_point(pElement->pContext->pLastMouseMoveTopLevelElement, pElement->pContext->lastMouseMovePosX, pElement->pContext->lastMouseMovePosY) == pElement;
|
|
}
|
|
|
|
|
|
|
|
//// Hierarchy ////
|
|
|
|
drgui_element* drgui_get_parent(drgui_element* pChildElement)
|
|
{
|
|
if (pChildElement == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pChildElement->pParent;
|
|
}
|
|
|
|
void drgui_detach(drgui_element* pChildElement)
|
|
{
|
|
if (pChildElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_element* pOldParent = pChildElement->pParent;
|
|
|
|
|
|
// We orphan the element using the private API. This will not mark the parent element as dirty so we need to do that afterwards.
|
|
drgui_detach_without_redraw(pChildElement);
|
|
|
|
// The region of the old parent needs to be redrawn.
|
|
if (pOldParent != NULL) {
|
|
drgui_auto_dirty(pOldParent, drgui_get_relative_rect(pOldParent));
|
|
}
|
|
}
|
|
|
|
void drgui_append(drgui_element* pChildElement, drgui_element* pParentElement)
|
|
{
|
|
if (pChildElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
// We first need to orphan the element. If the parent element is the new parent is the same as the old one, as in we
|
|
// are just moving the child element to the end of the children list, we want to delay the repaint until the end. To
|
|
// do this we use drgui_detach_without_redraw() because that will not trigger a redraw.
|
|
if (pChildElement->pParent != pParentElement) {
|
|
drgui_detach(pChildElement);
|
|
} else {
|
|
drgui_detach_without_redraw(pChildElement);
|
|
}
|
|
|
|
|
|
// Now we attach it to the end of the new parent.
|
|
if (pParentElement != NULL) {
|
|
drgui_append_without_detach(pChildElement, pParentElement);
|
|
}
|
|
}
|
|
|
|
void drgui_prepend(drgui_element* pChildElement, drgui_element* pParentElement)
|
|
{
|
|
if (pChildElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
// See comment in drgui_append() for explanation on this.
|
|
if (pChildElement->pParent != pParentElement) {
|
|
drgui_detach(pChildElement);
|
|
} else {
|
|
drgui_detach_without_redraw(pChildElement);
|
|
}
|
|
|
|
|
|
// Now we need to attach the element to the beginning of the parent.
|
|
if (pParentElement != NULL) {
|
|
drgui_prepend_without_detach(pChildElement, pParentElement);
|
|
}
|
|
}
|
|
|
|
void drgui_append_sibling(drgui_element* pElementToAppend, drgui_element* pElementToAppendTo)
|
|
{
|
|
if (pElementToAppend == NULL || pElementToAppendTo == NULL) {
|
|
return;
|
|
}
|
|
|
|
// See comment in drgui_append() for explanation on this.
|
|
if (pElementToAppend->pParent != pElementToAppendTo->pParent) {
|
|
drgui_detach(pElementToAppend);
|
|
} else {
|
|
drgui_detach_without_redraw(pElementToAppend);
|
|
}
|
|
|
|
|
|
// Now we need to attach the element such that it comes just after pElementToAppendTo
|
|
drgui_append_sibling_without_detach(pElementToAppend, pElementToAppendTo);
|
|
}
|
|
|
|
void drgui_prepend_sibling(drgui_element* pElementToPrepend, drgui_element* pElementToPrependTo)
|
|
{
|
|
if (pElementToPrepend == NULL || pElementToPrependTo == NULL) {
|
|
return;
|
|
}
|
|
|
|
// See comment in drgui_append() for explanation on this.
|
|
if (pElementToPrepend->pParent != pElementToPrependTo->pParent) {
|
|
drgui_detach(pElementToPrepend);
|
|
} else {
|
|
drgui_detach_without_redraw(pElementToPrepend);
|
|
}
|
|
|
|
|
|
// Now we need to attach the element such that it comes just after pElementToPrependTo
|
|
drgui_prepend_sibling_without_detach(pElementToPrepend, pElementToPrependTo);
|
|
}
|
|
|
|
drgui_element* drgui_find_top_level_element(drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (pElement->pParent != NULL) {
|
|
return drgui_find_top_level_element(pElement->pParent);
|
|
}
|
|
|
|
return pElement;
|
|
}
|
|
|
|
bool drgui_is_parent(drgui_element* pParentElement, drgui_element* pChildElement)
|
|
{
|
|
if (pParentElement == NULL || pChildElement == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pParentElement == pChildElement->pParent;
|
|
}
|
|
|
|
bool drgui_is_child(drgui_element* pChildElement, drgui_element* pParentElement)
|
|
{
|
|
return drgui_is_parent(pParentElement, pChildElement);
|
|
}
|
|
|
|
bool drgui_is_ancestor(drgui_element* pAncestorElement, drgui_element* pChildElement)
|
|
{
|
|
if (pAncestorElement == NULL || pChildElement == NULL) {
|
|
return false;
|
|
}
|
|
|
|
drgui_element* pParent = pChildElement->pParent;
|
|
while (pParent != NULL)
|
|
{
|
|
if (pParent == pAncestorElement) {
|
|
return true;
|
|
}
|
|
|
|
pParent = pParent->pParent;
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_is_descendant(drgui_element* pChildElement, drgui_element* pAncestorElement)
|
|
{
|
|
return drgui_is_ancestor(pAncestorElement, pChildElement);
|
|
}
|
|
|
|
bool drgui_is_self_or_ancestor(drgui_element* pAncestorElement, drgui_element* pChildElement)
|
|
{
|
|
return pAncestorElement == pChildElement || drgui_is_ancestor(pAncestorElement, pChildElement);
|
|
}
|
|
|
|
bool drgui_is_self_or_descendant(drgui_element* pChildElement, drgui_element* pAncestorElement)
|
|
{
|
|
return pChildElement == pAncestorElement || drgui_is_descendant(pChildElement, pAncestorElement);
|
|
}
|
|
|
|
|
|
|
|
//// Layout ////
|
|
|
|
void drgui_set_absolute_position(drgui_element* pElement, float positionX, float positionY)
|
|
{
|
|
if (pElement != NULL)
|
|
{
|
|
if (pElement->absolutePosX != positionX || pElement->absolutePosY != positionY)
|
|
{
|
|
float oldRelativePosX = drgui_get_relative_position_x(pElement);
|
|
float oldRelativePosY = drgui_get_relative_position_y(pElement);
|
|
|
|
drgui_begin_auto_dirty(pElement);
|
|
{
|
|
drgui_auto_dirty(pElement, drgui_get_local_rect(pElement)); // <-- Previous rectangle.
|
|
|
|
float offsetX = positionX - pElement->absolutePosX;
|
|
float offsetY = positionY - pElement->absolutePosY;
|
|
|
|
pElement->absolutePosX = positionX;
|
|
pElement->absolutePosY = positionY;
|
|
drgui_auto_dirty(pElement, drgui_get_local_rect(pElement)); // <-- New rectangle.
|
|
|
|
|
|
float newRelativePosX = drgui_get_relative_position_x(pElement);
|
|
float newRelativePosY = drgui_get_relative_position_y(pElement);
|
|
|
|
if (newRelativePosX != oldRelativePosX || newRelativePosY != oldRelativePosY) {
|
|
drgui_post_outbound_event_move(pElement, newRelativePosX, newRelativePosY);
|
|
}
|
|
|
|
|
|
drgui_apply_offset_to_children_recursive(pElement, offsetX, offsetY);
|
|
}
|
|
drgui_end_auto_dirty(pElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_get_absolute_position(const drgui_element* pElement, float * positionXOut, float * positionYOut)
|
|
{
|
|
if (pElement != NULL)
|
|
{
|
|
if (positionXOut != NULL) {
|
|
*positionXOut = pElement->absolutePosX;
|
|
}
|
|
|
|
if (positionYOut != NULL) {
|
|
*positionYOut = pElement->absolutePosY;
|
|
}
|
|
}
|
|
}
|
|
|
|
float drgui_get_absolute_position_x(const drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
return pElement->absolutePosX;
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
float drgui_get_absolute_position_y(const drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
return pElement->absolutePosY;
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
|
|
void drgui_set_relative_position(drgui_element* pElement, float relativePosX, float relativePosY)
|
|
{
|
|
if (pElement != NULL) {
|
|
if (pElement->pParent != NULL) {
|
|
drgui_set_absolute_position(pElement, pElement->pParent->absolutePosX + relativePosX, pElement->pParent->absolutePosY + relativePosY);
|
|
} else {
|
|
drgui_set_absolute_position(pElement, relativePosX, relativePosY);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_get_relative_position(const drgui_element* pElement, float* positionXOut, float* positionYOut)
|
|
{
|
|
if (pElement != NULL)
|
|
{
|
|
if (pElement->pParent != NULL)
|
|
{
|
|
if (positionXOut != NULL) {
|
|
*positionXOut = pElement->absolutePosX - pElement->pParent->absolutePosX;
|
|
}
|
|
|
|
if (positionYOut != NULL) {
|
|
*positionYOut = pElement->absolutePosY - pElement->pParent->absolutePosY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (positionXOut != NULL) {
|
|
*positionXOut = pElement->absolutePosX;
|
|
}
|
|
|
|
if (positionYOut != NULL) {
|
|
*positionYOut = pElement->absolutePosY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float drgui_get_relative_position_x(const drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
if (pElement->pParent != NULL) {
|
|
return pElement->absolutePosX - pElement->pParent->absolutePosX;
|
|
} else {
|
|
return pElement->absolutePosX;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
float drgui_get_relative_position_y(const drgui_element* pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
if (pElement->pParent != NULL) {
|
|
return pElement->absolutePosY - pElement->pParent->absolutePosY;
|
|
} else {
|
|
return pElement->absolutePosY;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void drgui_set_size(drgui_element* pElement, float width, float height)
|
|
{
|
|
if (pElement != NULL)
|
|
{
|
|
if (pElement->width != width || pElement->height != height)
|
|
{
|
|
drgui_begin_auto_dirty(pElement);
|
|
{
|
|
drgui_auto_dirty(pElement, drgui_get_local_rect(pElement)); // <-- Previous rectangle.
|
|
|
|
pElement->width = width;
|
|
pElement->height = height;
|
|
drgui_auto_dirty(pElement, drgui_get_local_rect(pElement)); // <-- New rectangle.
|
|
|
|
drgui_post_outbound_event_size(pElement, width, height);
|
|
}
|
|
drgui_end_auto_dirty(pElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_get_size(const drgui_element* pElement, float* widthOut, float* heightOut)
|
|
{
|
|
if (pElement != NULL) {
|
|
if (widthOut) *widthOut = pElement->width;
|
|
if (heightOut) *heightOut = pElement->height;
|
|
} else {
|
|
if (widthOut) *widthOut = 0;
|
|
if (heightOut) *heightOut = 0;
|
|
}
|
|
}
|
|
|
|
float drgui_get_width(const drgui_element * pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
return pElement->width;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
float drgui_get_height(const drgui_element * pElement)
|
|
{
|
|
if (pElement != NULL) {
|
|
return pElement->height;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
drgui_rect drgui_get_absolute_rect(const drgui_element* pElement)
|
|
{
|
|
drgui_rect rect;
|
|
if (pElement != NULL)
|
|
{
|
|
rect.left = pElement->absolutePosX;
|
|
rect.top = pElement->absolutePosY;
|
|
rect.right = rect.left + pElement->width;
|
|
rect.bottom = rect.top + pElement->height;
|
|
}
|
|
else
|
|
{
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
rect.right = 0;
|
|
rect.bottom = 0;
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
drgui_rect drgui_get_relative_rect(const drgui_element* pElement)
|
|
{
|
|
drgui_rect rect;
|
|
if (pElement != NULL)
|
|
{
|
|
rect.left = drgui_get_relative_position_x(pElement);
|
|
rect.top = drgui_get_relative_position_y(pElement);
|
|
rect.right = rect.left + pElement->width;
|
|
rect.bottom = rect.top + pElement->height;
|
|
}
|
|
else
|
|
{
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
rect.right = 0;
|
|
rect.bottom = 0;
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
drgui_rect drgui_get_local_rect(const drgui_element* pElement)
|
|
{
|
|
drgui_rect rect;
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
|
|
if (pElement != NULL)
|
|
{
|
|
rect.right = pElement->width;
|
|
rect.bottom = pElement->height;
|
|
}
|
|
else
|
|
{
|
|
rect.right = 0;
|
|
rect.bottom = 0;
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
|
|
|
|
//// Painting ////
|
|
|
|
bool drgui_register_painting_callbacks(drgui_context* pContext, void* pPaintingContext, drgui_painting_callbacks callbacks)
|
|
{
|
|
if (pContext == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Fail if the painting callbacks have already been registered.
|
|
if (pContext->pPaintingContext != NULL) {
|
|
return false;
|
|
}
|
|
|
|
|
|
pContext->pPaintingContext = pPaintingContext;
|
|
pContext->paintingCallbacks = callbacks;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool drgui_iterate_visible_elements(drgui_element* pParentElement, drgui_rect relativeRect, drgui_visible_iteration_proc callback, void* pUserData)
|
|
{
|
|
if (pParentElement == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (callback == NULL) {
|
|
return false;
|
|
}
|
|
|
|
|
|
if (!drgui_is_visible(pParentElement)) {
|
|
return true;
|
|
}
|
|
|
|
drgui_rect clampedRelativeRect = relativeRect;
|
|
if (drgui_clamp_rect_to_element(pParentElement, &clampedRelativeRect))
|
|
{
|
|
// We'll only get here if some part of the rectangle was inside the element.
|
|
if (!callback(pParentElement, &clampedRelativeRect, pUserData)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (drgui_element* pChild = pParentElement->pFirstChild; pChild != NULL; pChild = pChild->pNextSibling)
|
|
{
|
|
float childRelativePosX = drgui_get_relative_position_x(pChild);
|
|
float childRelativePosY = drgui_get_relative_position_y(pChild);
|
|
|
|
drgui_rect childRect;
|
|
if (drgui_is_clipping_enabled(pChild)) {
|
|
childRect = clampedRelativeRect;
|
|
} else {
|
|
childRect = relativeRect;
|
|
}
|
|
|
|
|
|
childRect.left -= childRelativePosX;
|
|
childRect.top -= childRelativePosY;
|
|
childRect.right -= childRelativePosX;
|
|
childRect.bottom -= childRelativePosY;
|
|
|
|
if (!drgui_iterate_visible_elements(pChild, childRect, callback, pUserData)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
void drgui_disable_auto_dirty(drgui_context* pContext)
|
|
{
|
|
if (pContext != NULL) {
|
|
pContext->flags |= IS_AUTO_DIRTY_DISABLED;
|
|
}
|
|
}
|
|
|
|
void drgui_enable_auto_dirty(drgui_context* pContext)
|
|
{
|
|
if (pContext != NULL) {
|
|
pContext->flags &= ~IS_AUTO_DIRTY_DISABLED;
|
|
}
|
|
}
|
|
|
|
bool drgui_is_auto_dirty_enabled(drgui_context* pContext)
|
|
{
|
|
if (pContext != NULL) {
|
|
return (pContext->flags & IS_AUTO_DIRTY_DISABLED) == 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
drgui_element* drgui_begin_dirty(drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_context* pContext = pElement->pContext;
|
|
assert(pContext != NULL);
|
|
|
|
drgui_element* pTopLevelElement = drgui_find_top_level_element(pElement);
|
|
assert(pTopLevelElement != NULL);
|
|
|
|
// The element needs to be added to the list of dirty elements if it doesn't exist already.
|
|
bool isAlreadyDirty = false;
|
|
for (size_t iDirtyElementCount = 0; iDirtyElementCount < pContext->dirtyElementCount; ++iDirtyElementCount) {
|
|
if (pContext->ppDirtyElements[iDirtyElementCount] == pTopLevelElement) {
|
|
isAlreadyDirty = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isAlreadyDirty) {
|
|
if (pContext->dirtyElementCount == pContext->dirtyElementBufferSize) {
|
|
size_t newBufferSize = pContext->dirtyElementBufferSize == 0 ? 1 : pContext->dirtyElementBufferSize*2;
|
|
drgui_element** ppNewDirtyElements = (drgui_element**)realloc(pContext->ppDirtyElements, newBufferSize * sizeof(*ppNewDirtyElements));
|
|
if (ppNewDirtyElements == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
pContext->ppDirtyElements = ppNewDirtyElements;
|
|
pContext->dirtyElementBufferSize = newBufferSize;
|
|
}
|
|
|
|
pContext->ppDirtyElements[pContext->dirtyCounter] = pTopLevelElement;
|
|
pContext->dirtyElementCount += 1;
|
|
}
|
|
|
|
|
|
pContext->dirtyCounter += 1;
|
|
return pTopLevelElement;
|
|
}
|
|
|
|
void drgui_end_dirty(drgui_element* pElement)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_context* pContext = pElement->pContext;
|
|
assert(pContext != NULL);
|
|
|
|
assert(pContext->dirtyElementCount > 0);
|
|
assert(pContext->dirtyCounter > 0);
|
|
|
|
pContext->dirtyCounter -= 1;
|
|
if (pContext->dirtyCounter == 0)
|
|
{
|
|
for (size_t i = 0; i < pContext->dirtyElementCount; ++i) {
|
|
drgui_post_outbound_event_dirty_global(pContext->ppDirtyElements[i], pContext->ppDirtyElements[i]->dirtyRect);
|
|
pContext->ppDirtyElements[i]->dirtyRect = drgui_make_inside_out_rect();
|
|
}
|
|
|
|
pContext->dirtyElementCount = 0;
|
|
}
|
|
}
|
|
|
|
void drgui_dirty(drgui_element* pElement, drgui_rect relativeRect)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
//drgui_context* pContext = pElement->pContext;
|
|
//assert(pContext != NULL);
|
|
|
|
drgui_element* pTopLevelElement = drgui_begin_dirty(pElement);
|
|
if (pTopLevelElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTopLevelElement->dirtyRect = drgui_rect_union(pTopLevelElement->dirtyRect, drgui_make_rect_absolute(pElement, &relativeRect));
|
|
drgui_end_dirty(pElement);
|
|
}
|
|
|
|
|
|
bool drgui_draw_iteration_callback(drgui_element* pElement, drgui_rect* pRelativeRect, void* pUserData)
|
|
{
|
|
assert(pElement != NULL);
|
|
assert(pRelativeRect != NULL);
|
|
|
|
if (pElement->onPaint != NULL)
|
|
{
|
|
// We want to set the initial clipping rectangle before drawing.
|
|
drgui_set_clip(pElement, *pRelativeRect, pUserData);
|
|
|
|
// We now call the painting function, but only after setting the clipping rectangle.
|
|
pElement->onPaint(pElement, *pRelativeRect, pUserData);
|
|
|
|
// The on_paint event handler may have adjusted the clipping rectangle so we need to ensure it's restored.
|
|
drgui_set_clip(pElement, *pRelativeRect, pUserData);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void drgui_draw(drgui_element* pElement, drgui_rect relativeRect, void* pPaintData)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_context* pContext = pElement->pContext;
|
|
if (pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pContext->paintingCallbacks.drawBegin != NULL);
|
|
assert(pContext->paintingCallbacks.drawEnd != NULL);
|
|
|
|
pContext->paintingCallbacks.drawBegin(pPaintData);
|
|
{
|
|
drgui_iterate_visible_elements(pElement, relativeRect, drgui_draw_iteration_callback, pPaintData);
|
|
}
|
|
pContext->paintingCallbacks.drawEnd(pPaintData);
|
|
}
|
|
|
|
void drgui_get_clip(drgui_element* pElement, drgui_rect* pRelativeRect, void* pPaintData)
|
|
{
|
|
if (pElement == NULL || pElement->pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
pElement->pContext->paintingCallbacks.getClip(pRelativeRect, pPaintData);
|
|
|
|
// The clip returned by the drawing callback will be absolute so we'll need to convert that to relative.
|
|
drgui_make_rect_relative(pElement, pRelativeRect);
|
|
}
|
|
|
|
void drgui_set_clip(drgui_element* pElement, drgui_rect relativeRect, void* pPaintData)
|
|
{
|
|
if (pElement == NULL || pElement->pContext == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
// Make sure the rectangle is not negative.
|
|
if (relativeRect.right < relativeRect.left) {
|
|
relativeRect.right = relativeRect.left;
|
|
}
|
|
|
|
if (relativeRect.bottom < relativeRect.top) {
|
|
relativeRect.bottom = relativeRect.top;
|
|
}
|
|
|
|
drgui_rect absoluteRect = relativeRect;
|
|
drgui_make_rect_absolute(pElement, &absoluteRect);
|
|
|
|
pElement->pContext->paintingCallbacks.setClip(absoluteRect, pPaintData);
|
|
}
|
|
|
|
void drgui_draw_rect(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, void* pPaintData)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pElement->pContext != NULL);
|
|
|
|
drgui_rect absoluteRect = relativeRect;
|
|
drgui_make_rect_absolute(pElement, &absoluteRect);
|
|
|
|
pElement->pContext->paintingCallbacks.drawRect(absoluteRect, color, pPaintData);
|
|
}
|
|
|
|
void drgui_draw_rect_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float outlineWidth, void* pPaintData)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pElement->pContext != NULL);
|
|
|
|
drgui_rect absoluteRect = relativeRect;
|
|
drgui_make_rect_absolute(pElement, &absoluteRect);
|
|
|
|
pElement->pContext->paintingCallbacks.drawRectOutline(absoluteRect, color, outlineWidth, pPaintData);
|
|
}
|
|
|
|
void drgui_draw_rect_with_outline(drgui_element * pElement, drgui_rect relativeRect, drgui_color color, float outlineWidth, drgui_color outlineColor, void * pPaintData)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pElement->pContext != NULL);
|
|
|
|
drgui_rect absoluteRect = relativeRect;
|
|
drgui_make_rect_absolute(pElement, &absoluteRect);
|
|
|
|
pElement->pContext->paintingCallbacks.drawRectWithOutline(absoluteRect, color, outlineWidth, outlineColor, pPaintData);
|
|
}
|
|
|
|
void drgui_draw_round_rect(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, void* pPaintData)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pElement->pContext != NULL);
|
|
|
|
drgui_rect absoluteRect = relativeRect;
|
|
drgui_make_rect_absolute(pElement, &absoluteRect);
|
|
|
|
pElement->pContext->paintingCallbacks.drawRoundRect(absoluteRect, color, radius, pPaintData);
|
|
}
|
|
|
|
void drgui_draw_round_rect_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, void* pPaintData)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pElement->pContext != NULL);
|
|
|
|
drgui_rect absoluteRect = relativeRect;
|
|
drgui_make_rect_absolute(pElement, &absoluteRect);
|
|
|
|
pElement->pContext->paintingCallbacks.drawRoundRectOutline(absoluteRect, color, radius, outlineWidth, pPaintData);
|
|
}
|
|
|
|
void drgui_draw_round_rect_with_outline(drgui_element* pElement, drgui_rect relativeRect, drgui_color color, float radius, float outlineWidth, drgui_color outlineColor, void* pPaintData)
|
|
{
|
|
if (pElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pElement->pContext != NULL);
|
|
|
|
drgui_rect absoluteRect = relativeRect;
|
|
drgui_make_rect_absolute(pElement, &absoluteRect);
|
|
|
|
pElement->pContext->paintingCallbacks.drawRoundRectWithOutline(absoluteRect, color, radius, outlineWidth, outlineColor, pPaintData);
|
|
}
|
|
|
|
void drgui_draw_text(drgui_element* pElement, drgui_font* pFont, const char* text, int textLengthInBytes, float posX, float posY, drgui_color color, drgui_color backgroundColor, void* pPaintData)
|
|
{
|
|
if (pElement == NULL || pFont == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pElement->pContext != NULL);
|
|
|
|
float absolutePosX = posX;
|
|
float absolutePosY = posY;
|
|
drgui_make_point_absolute(pElement, &absolutePosX, &absolutePosY);
|
|
|
|
pElement->pContext->paintingCallbacks.drawText(pFont->internalFont, text, textLengthInBytes, absolutePosX, absolutePosY, color, backgroundColor, pPaintData);
|
|
}
|
|
|
|
void drgui_draw_image(drgui_element* pElement, drgui_image* pImage, drgui_draw_image_args* pArgs, void* pPaintData)
|
|
{
|
|
if (pElement == NULL || pImage == NULL || pArgs == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pElement->pContext != NULL);
|
|
|
|
drgui_make_point_absolute(pElement, &pArgs->dstX, &pArgs->dstY);
|
|
drgui_make_point_absolute(pElement, &pArgs->dstBoundsX, &pArgs->dstBoundsY);
|
|
|
|
if ((pArgs->options & DRGUI_IMAGE_ALIGN_CENTER) != 0)
|
|
{
|
|
pArgs->dstX = pArgs->dstBoundsX + (pArgs->dstBoundsWidth - pArgs->dstWidth) / 2;
|
|
pArgs->dstY = pArgs->dstBoundsY + (pArgs->dstBoundsHeight - pArgs->dstHeight) / 2;
|
|
}
|
|
|
|
drgui_rect prevClip;
|
|
pElement->pContext->paintingCallbacks.getClip(&prevClip, pPaintData);
|
|
|
|
bool restoreClip = false;
|
|
if ((pArgs->options & DRGUI_IMAGE_CLIP_BOUNDS) != 0)
|
|
{
|
|
// We only need to clip if part of the destination rectangle falls outside of the bounds.
|
|
if (pArgs->dstX < pArgs->dstBoundsX || pArgs->dstX + pArgs->dstWidth > pArgs->dstBoundsX + pArgs->dstBoundsWidth ||
|
|
pArgs->dstY < pArgs->dstBoundsY || pArgs->dstY + pArgs->dstHeight > pArgs->dstBoundsY + pArgs->dstBoundsHeight)
|
|
{
|
|
restoreClip = true;
|
|
pElement->pContext->paintingCallbacks.setClip(drgui_make_rect(pArgs->dstBoundsX, pArgs->dstBoundsY, pArgs->dstBoundsX + pArgs->dstBoundsWidth, pArgs->dstBoundsY + pArgs->dstBoundsHeight), pPaintData);
|
|
}
|
|
}
|
|
|
|
if ((pArgs->options & DRGUI_IMAGE_DRAW_BOUNDS) != 0)
|
|
{
|
|
// The bounds is the area sitting around the outside of the destination rectangle.
|
|
const float boundsLeft = pArgs->dstBoundsX;
|
|
const float boundsTop = pArgs->dstBoundsY;
|
|
const float boundsRight = boundsLeft + pArgs->dstBoundsWidth;
|
|
const float boundsBottom = boundsTop + pArgs->dstBoundsHeight;
|
|
|
|
const float imageLeft = pArgs->dstX;
|
|
const float imageTop = pArgs->dstY;
|
|
const float imageRight = imageLeft + pArgs->dstWidth;
|
|
const float imageBottom = imageTop + pArgs->dstHeight;
|
|
|
|
// Left.
|
|
if (boundsLeft < imageLeft) {
|
|
pElement->pContext->paintingCallbacks.drawRect(drgui_make_rect(boundsLeft, boundsTop, imageLeft, boundsBottom), pArgs->boundsColor, pPaintData);
|
|
}
|
|
|
|
// Right.
|
|
if (boundsRight > imageRight) {
|
|
pElement->pContext->paintingCallbacks.drawRect(drgui_make_rect(imageRight, boundsTop, boundsRight, boundsBottom), pArgs->boundsColor, pPaintData);
|
|
}
|
|
|
|
// Top.
|
|
if (boundsTop < imageTop) {
|
|
pElement->pContext->paintingCallbacks.drawRect(drgui_make_rect(imageLeft, boundsTop, imageRight, imageTop), pArgs->boundsColor, pPaintData);
|
|
}
|
|
|
|
// Bottom.
|
|
if (boundsBottom > imageBottom) {
|
|
pElement->pContext->paintingCallbacks.drawRect(drgui_make_rect(imageLeft, imageBottom, imageRight, boundsBottom), pArgs->boundsColor, pPaintData);
|
|
}
|
|
}
|
|
|
|
pElement->pContext->paintingCallbacks.drawImage(pImage->hResource, pArgs, pPaintData);
|
|
|
|
if (restoreClip) {
|
|
pElement->pContext->paintingCallbacks.setClip(prevClip, pPaintData);
|
|
}
|
|
}
|
|
|
|
|
|
drgui_font* drgui_create_font(drgui_context* pContext, const char* family, unsigned int size, drgui_font_weight weight, drgui_font_slant slant, float rotation, unsigned int flags)
|
|
{
|
|
if (pContext == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (pContext->paintingCallbacks.createFont == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
|
|
drgui_resource internalFont = pContext->paintingCallbacks.createFont(pContext->pPaintingContext, family, size, weight, slant, rotation, flags);
|
|
if (internalFont == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_font* pFont = (drgui_font*)malloc(sizeof(drgui_font));
|
|
if (pFont == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
pFont->pContext = pContext;
|
|
pFont->family[0] = '\0';
|
|
pFont->size = size;
|
|
pFont->weight = weight;
|
|
pFont->slant = slant;
|
|
pFont->rotation = rotation;
|
|
pFont->flags = flags;
|
|
pFont->internalFont = internalFont;
|
|
|
|
if (family != NULL) {
|
|
drgui__strcpy_s(pFont->family, sizeof(pFont->family), family);
|
|
}
|
|
|
|
return pFont;
|
|
}
|
|
|
|
void drgui_delete_font(drgui_font* pFont)
|
|
{
|
|
if (pFont == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pFont->pContext != NULL);
|
|
|
|
// Delete the internal font objects first.
|
|
if (pFont->pContext->paintingCallbacks.deleteFont) {
|
|
pFont->pContext->paintingCallbacks.deleteFont(pFont->internalFont);
|
|
}
|
|
|
|
free(pFont);
|
|
}
|
|
|
|
bool drgui_get_font_metrics(drgui_font* pFont, drgui_font_metrics* pMetricsOut)
|
|
{
|
|
if (pFont == NULL || pMetricsOut == NULL) {
|
|
return false;
|
|
}
|
|
|
|
assert(pFont->pContext != NULL);
|
|
|
|
if (pFont->pContext->paintingCallbacks.getFontMetrics == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pFont->pContext->paintingCallbacks.getFontMetrics(pFont->internalFont, pMetricsOut);
|
|
}
|
|
|
|
bool drgui_get_glyph_metrics(drgui_font* pFont, unsigned int utf32, drgui_glyph_metrics* pMetricsOut)
|
|
{
|
|
if (pFont == NULL || pMetricsOut == NULL) {
|
|
return false;
|
|
}
|
|
|
|
assert(pFont->pContext != NULL);
|
|
|
|
if (pFont->pContext->paintingCallbacks.getGlyphMetrics == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pFont->pContext->paintingCallbacks.getGlyphMetrics(pFont->internalFont, utf32, pMetricsOut);
|
|
}
|
|
|
|
bool drgui_measure_string(drgui_font* pFont, const char* text, size_t textLengthInBytes, float* pWidthOut, float* pHeightOut)
|
|
{
|
|
if (pFont == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (text == NULL || textLengthInBytes == 0)
|
|
{
|
|
drgui_font_metrics metrics;
|
|
if (!drgui_get_font_metrics(pFont, &metrics)) {
|
|
return false;
|
|
}
|
|
|
|
if (pWidthOut) {
|
|
*pWidthOut = 0;
|
|
}
|
|
if (pHeightOut) {
|
|
*pHeightOut = (float)metrics.lineHeight;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
assert(pFont->pContext != NULL);
|
|
|
|
if (pFont->pContext->paintingCallbacks.measureString == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pFont->pContext->paintingCallbacks.measureString(pFont->internalFont, text, textLengthInBytes, pWidthOut, pHeightOut);
|
|
}
|
|
|
|
bool drgui_get_text_cursor_position_from_point(drgui_font* pFont, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut)
|
|
{
|
|
if (pFont == NULL) {
|
|
return false;
|
|
}
|
|
|
|
assert(pFont->pContext != NULL);
|
|
|
|
if (pFont->pContext->paintingCallbacks.getTextCursorPositionFromPoint) {
|
|
return pFont->pContext->paintingCallbacks.getTextCursorPositionFromPoint(pFont->internalFont, text, textSizeInBytes, maxWidth, inputPosX, pTextCursorPosXOut, pCharacterIndexOut);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_get_text_cursor_position_from_char(drgui_font* pFont, const char* text, size_t characterIndex, float* pTextCursorPosXOut)
|
|
{
|
|
if (pFont == NULL) {
|
|
return false;
|
|
}
|
|
|
|
assert(pFont->pContext != NULL);
|
|
|
|
if (pFont->pContext->paintingCallbacks.getTextCursorPositionFromChar) {
|
|
return pFont->pContext->paintingCallbacks.getTextCursorPositionFromChar(pFont->internalFont, text, characterIndex, pTextCursorPosXOut);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
drgui_image* drgui_create_image(drgui_context* pContext, unsigned int width, unsigned int height, drgui_image_format format, unsigned int stride, const void* pData)
|
|
{
|
|
if (pContext == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (pContext->paintingCallbacks.createImage == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// If the stride is 0, assume tightly packed.
|
|
if (stride == 0) {
|
|
stride = width * 4;
|
|
}
|
|
|
|
|
|
drgui_resource internalImage = pContext->paintingCallbacks.createImage(pContext->pPaintingContext, width, height, format, stride, pData);
|
|
if (internalImage == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_image* pImage = (drgui_image*)malloc(sizeof(*pImage));
|
|
if (pImage == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
pImage->pContext = pContext;
|
|
pImage->hResource = internalImage;
|
|
|
|
|
|
return pImage;
|
|
}
|
|
|
|
void drgui_delete_image(drgui_image* pImage)
|
|
{
|
|
if (pImage == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pImage->pContext != NULL);
|
|
|
|
// Delete the internal font object.
|
|
if (pImage->pContext->paintingCallbacks.deleteImage) {
|
|
pImage->pContext->paintingCallbacks.deleteImage(pImage->hResource);
|
|
}
|
|
|
|
// Free the font object last.
|
|
free(pImage);
|
|
}
|
|
|
|
void drgui_get_image_size(drgui_image* pImage, unsigned int* pWidthOut, unsigned int* pHeightOut)
|
|
{
|
|
if (pWidthOut) *pWidthOut = 0;
|
|
if (pHeightOut) *pHeightOut = 0;
|
|
|
|
if (pImage == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pImage->pContext != NULL);
|
|
|
|
if (pImage->pContext->paintingCallbacks.getImageSize == NULL) {
|
|
return;
|
|
}
|
|
|
|
pImage->pContext->paintingCallbacks.getImageSize(pImage->hResource, pWidthOut, pHeightOut);
|
|
}
|
|
|
|
drgui_image_format drgui_get_optimal_image_format(drgui_context* pContext)
|
|
{
|
|
if (pContext == NULL || pContext->paintingCallbacks.getOptimalImageFormat == NULL) {
|
|
return drgui_image_format_rgba8;
|
|
}
|
|
|
|
return pContext->paintingCallbacks.getOptimalImageFormat(pContext->pPaintingContext);
|
|
}
|
|
|
|
void* drgui_map_image_data(drgui_image* pImage, unsigned int accessFlags)
|
|
{
|
|
if (pImage == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (pImage->pContext->paintingCallbacks.mapImageData == NULL || pImage->pContext->paintingCallbacks.unmapImageData == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pImage->pContext->paintingCallbacks.mapImageData(pImage->hResource, accessFlags);
|
|
}
|
|
|
|
void drgui_unmap_image_data(drgui_image* pImage)
|
|
{
|
|
if (pImage == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pImage->pContext->paintingCallbacks.unmapImageData == NULL) {
|
|
return;
|
|
}
|
|
|
|
pImage->pContext->paintingCallbacks.unmapImageData(pImage->hResource);
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// HIGH-LEVEL API
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
//// Hit Testing and Layout ////
|
|
|
|
void drgui_on_size_fit_children_to_parent(drgui_element* pElement, float newWidth, float newHeight)
|
|
{
|
|
for (drgui_element* pChild = pElement->pFirstChild; pChild != NULL; pChild = pChild->pNextSibling) {
|
|
drgui_set_size(pChild, newWidth, newHeight);
|
|
}
|
|
}
|
|
|
|
bool drgui_pass_through_hit_test(drgui_element* pElement, float mousePosX, float mousePosY)
|
|
{
|
|
(void)pElement;
|
|
(void)mousePosX;
|
|
(void)mousePosY;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
//// Painting ////
|
|
|
|
void drgui_draw_border(drgui_element* pElement, float borderWidth, drgui_color color, void* pUserData)
|
|
{
|
|
drgui_draw_rect_outline(pElement, drgui_get_local_rect(pElement), color, borderWidth, pUserData);
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// UTILITY API
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
|
|
drgui_color drgui_rgba(drgui_byte r, drgui_byte g, drgui_byte b, drgui_byte a)
|
|
{
|
|
drgui_color color;
|
|
color.r = r;
|
|
color.g = g;
|
|
color.b = b;
|
|
color.a = a;
|
|
|
|
return color;
|
|
}
|
|
|
|
drgui_color drgui_rgb(drgui_byte r, drgui_byte g, drgui_byte b)
|
|
{
|
|
drgui_color color;
|
|
color.r = r;
|
|
color.g = g;
|
|
color.b = b;
|
|
color.a = 255;
|
|
|
|
return color;
|
|
}
|
|
|
|
drgui_rect drgui_clamp_rect(drgui_rect rect, drgui_rect other)
|
|
{
|
|
drgui_rect result;
|
|
result.left = (rect.left >= other.left) ? rect.left : other.left;
|
|
result.top = (rect.top >= other.top) ? rect.top : other.top;
|
|
result.right = (rect.right <= other.right) ? rect.right : other.right;
|
|
result.bottom = (rect.bottom <= other.bottom) ? rect.bottom : other.bottom;
|
|
|
|
return result;
|
|
}
|
|
|
|
bool drgui_clamp_rect_to_element(const drgui_element* pElement, drgui_rect* pRelativeRect)
|
|
{
|
|
if (pElement == NULL || pRelativeRect == NULL) {
|
|
return false;
|
|
}
|
|
|
|
|
|
if (pRelativeRect->left < 0) {
|
|
pRelativeRect->left = 0;
|
|
}
|
|
if (pRelativeRect->top < 0) {
|
|
pRelativeRect->top = 0;
|
|
}
|
|
|
|
if (pRelativeRect->right > pElement->width) {
|
|
pRelativeRect->right = pElement->width;
|
|
}
|
|
if (pRelativeRect->bottom > pElement->height) {
|
|
pRelativeRect->bottom = pElement->height;
|
|
}
|
|
|
|
|
|
return (pRelativeRect->right - pRelativeRect->left > 0) && (pRelativeRect->bottom - pRelativeRect->top > 0);
|
|
}
|
|
|
|
drgui_rect drgui_make_rect_relative(const drgui_element* pElement, drgui_rect* pRect)
|
|
{
|
|
if (pElement == NULL || pRect == NULL) {
|
|
return drgui_make_rect(0, 0, 0, 0);
|
|
}
|
|
|
|
pRect->left -= pElement->absolutePosX;
|
|
pRect->top -= pElement->absolutePosY;
|
|
pRect->right -= pElement->absolutePosX;
|
|
pRect->bottom -= pElement->absolutePosY;
|
|
|
|
return *pRect;
|
|
}
|
|
|
|
drgui_rect drgui_make_rect_absolute(const drgui_element * pElement, drgui_rect * pRect)
|
|
{
|
|
if (pElement == NULL || pRect == NULL) {
|
|
return drgui_make_rect(0, 0, 0, 0);
|
|
}
|
|
|
|
pRect->left += pElement->absolutePosX;
|
|
pRect->top += pElement->absolutePosY;
|
|
pRect->right += pElement->absolutePosX;
|
|
pRect->bottom += pElement->absolutePosY;
|
|
|
|
return *pRect;
|
|
}
|
|
|
|
void drgui_make_point_relative(const drgui_element* pElement, float* positionX, float* positionY)
|
|
{
|
|
if (pElement != NULL)
|
|
{
|
|
if (positionX != NULL) {
|
|
*positionX -= pElement->absolutePosX;
|
|
}
|
|
|
|
if (positionY != NULL) {
|
|
*positionY -= pElement->absolutePosY;
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_make_point_absolute(const drgui_element* pElement, float* positionX, float* positionY)
|
|
{
|
|
if (pElement != NULL)
|
|
{
|
|
if (positionX != NULL) {
|
|
*positionX += pElement->absolutePosX;
|
|
}
|
|
|
|
if (positionY != NULL) {
|
|
*positionY += pElement->absolutePosY;
|
|
}
|
|
}
|
|
}
|
|
|
|
drgui_rect drgui_make_rect(float left, float top, float right, float bottom)
|
|
{
|
|
drgui_rect rect;
|
|
rect.left = left;
|
|
rect.top = top;
|
|
rect.right = right;
|
|
rect.bottom = bottom;
|
|
|
|
return rect;
|
|
}
|
|
|
|
drgui_rect drgui_make_inside_out_rect()
|
|
{
|
|
drgui_rect rect;
|
|
rect.left = FLT_MAX;
|
|
rect.top = FLT_MAX;
|
|
rect.right = -FLT_MAX;
|
|
rect.bottom = -FLT_MAX;
|
|
|
|
return rect;
|
|
}
|
|
|
|
drgui_rect drgui_grow_rect(drgui_rect rect, float amount)
|
|
{
|
|
drgui_rect result = rect;
|
|
result.left -= amount;
|
|
result.top -= amount;
|
|
result.right += amount;
|
|
result.bottom += amount;
|
|
|
|
return result;
|
|
}
|
|
|
|
drgui_rect drgui_scale_rect(drgui_rect rect, float scaleX, float scaleY)
|
|
{
|
|
drgui_rect result = rect;
|
|
result.left *= scaleX;
|
|
result.top *= scaleY;
|
|
result.right *= scaleX;
|
|
result.bottom *= scaleY;
|
|
|
|
return result;
|
|
}
|
|
|
|
drgui_rect drgui_offset_rect(drgui_rect rect, float offsetX, float offsetY)
|
|
{
|
|
return drgui_make_rect(rect.left + offsetX, rect.top + offsetY, rect.right + offsetX, rect.bottom + offsetY);
|
|
}
|
|
|
|
drgui_rect drgui_rect_union(drgui_rect rect0, drgui_rect rect1)
|
|
{
|
|
drgui_rect result;
|
|
result.left = (rect0.left < rect1.left) ? rect0.left : rect1.left;
|
|
result.top = (rect0.top < rect1.top) ? rect0.top : rect1.top;
|
|
result.right = (rect0.right > rect1.right) ? rect0.right : rect1.right;
|
|
result.bottom = (rect0.bottom > rect1.bottom) ? rect0.bottom : rect1.bottom;
|
|
|
|
return result;
|
|
}
|
|
|
|
bool drgui_rect_contains_point(drgui_rect rect, float posX, float posY)
|
|
{
|
|
if (posX < rect.left || posY < rect.top) {
|
|
return false;
|
|
}
|
|
|
|
if (posX >= rect.right || posY >= rect.bottom) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool drgui_rect_equal(drgui_rect rect0, drgui_rect rect1)
|
|
{
|
|
return
|
|
rect0.left == rect1.left &&
|
|
rect0.top == rect1.top &&
|
|
rect0.right == rect1.right &&
|
|
rect0.bottom == rect1.bottom;
|
|
}
|
|
|
|
bool drgui_rect_has_volume(drgui_rect rect)
|
|
{
|
|
return rect.right > rect.left && rect.bottom > rect.top;
|
|
}
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// EASY_DRAW-SPECIFIC API
|
|
//
|
|
/////////////////////////////////////////////////////////////////
|
|
#ifndef DRGUI_NO_DR_2D
|
|
|
|
void drgui_draw_begin_dr_2d(void* pPaintData);
|
|
void drgui_draw_end_dr_2d(void* pPaintData);
|
|
void drgui_set_clip_dr_2d(drgui_rect rect, void* pPaintData);
|
|
void drgui_get_clip_dr_2d(drgui_rect* pRectOut, void* pPaintData);
|
|
void drgui_draw_rect_dr_2d(drgui_rect rect, drgui_color color, void* pPaintData);
|
|
void drgui_draw_rect_outline_dr_2d(drgui_rect, drgui_color, float, void*);
|
|
void drgui_draw_rect_with_outline_dr_2d(drgui_rect, drgui_color, float, drgui_color, void*);
|
|
void drgui_draw_round_rect_dr_2d(drgui_rect, drgui_color, float, void*);
|
|
void drgui_draw_round_rect_outline_dr_2d(drgui_rect, drgui_color, float, float, void*);
|
|
void drgui_draw_round_rect_with_outline_dr_2d(drgui_rect, drgui_color, float, float, drgui_color, void*);
|
|
void drgui_draw_text_dr_2d(drgui_resource, const char*, int, float, float, drgui_color, drgui_color, void*);
|
|
void drgui_draw_image_dr_2d(drgui_resource image, drgui_draw_image_args* pArgs, void* pPaintData);
|
|
|
|
drgui_resource drgui_create_font_dr_2d(void*, const char*, unsigned int, drgui_font_weight, drgui_font_slant, float, unsigned int flags);
|
|
void drgui_delete_font_dr_2d(drgui_resource);
|
|
unsigned int drgui_get_font_size_dr_2d(drgui_resource hFont);
|
|
bool drgui_get_font_metrics_dr_2d(drgui_resource, drgui_font_metrics*);
|
|
bool drgui_get_glyph_metrics_dr_2d(drgui_resource, unsigned int, drgui_glyph_metrics*);
|
|
bool drgui_measure_string_dr_2d(drgui_resource, const char*, size_t, float*, float*);
|
|
bool drgui_get_text_cursor_position_from_point_dr_2d(drgui_resource font, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut);
|
|
bool drgui_get_text_cursor_position_from_char_dr_2d(drgui_resource font, const char* text, size_t characterIndex, float* pTextCursorPosXOut);
|
|
|
|
drgui_resource drgui_create_image_dr_2d(void* pPaintingContext, unsigned int width, unsigned int height, drgui_image_format format, unsigned int stride, const void* pImageData);
|
|
void drgui_delete_image_dr_2d(drgui_resource image);
|
|
void drgui_get_image_size_dr_2d(drgui_resource image, unsigned int* pWidthOut, unsigned int* pHeightOut);
|
|
drgui_image_format drgui_get_optimal_image_format_dr_2d(void* pPaintingContext);
|
|
void* drgui_map_image_data_dr_2d(drgui_resource image, unsigned int accessFlags);
|
|
void drgui_unmap_image_data_dr_2d(drgui_resource image);
|
|
|
|
drgui_context* drgui_create_context_dr_2d(dr2d_context* pDrawingContext)
|
|
{
|
|
drgui_context* pContext = drgui_create_context();
|
|
if (pContext != NULL) {
|
|
drgui_register_dr_2d_callbacks(pContext, pDrawingContext);
|
|
}
|
|
|
|
return pContext;
|
|
}
|
|
|
|
void drgui_register_dr_2d_callbacks(drgui_context* pContext, dr2d_context* pDrawingContext)
|
|
{
|
|
drgui_painting_callbacks callbacks;
|
|
callbacks.drawBegin = drgui_draw_begin_dr_2d;
|
|
callbacks.drawEnd = drgui_draw_end_dr_2d;
|
|
callbacks.setClip = drgui_set_clip_dr_2d;
|
|
callbacks.getClip = drgui_get_clip_dr_2d;
|
|
callbacks.drawRect = drgui_draw_rect_dr_2d;
|
|
callbacks.drawRectOutline = drgui_draw_rect_outline_dr_2d;
|
|
callbacks.drawRectWithOutline = drgui_draw_rect_with_outline_dr_2d;
|
|
callbacks.drawRoundRect = drgui_draw_round_rect_dr_2d;
|
|
callbacks.drawRoundRectOutline = drgui_draw_round_rect_outline_dr_2d;
|
|
callbacks.drawRoundRectWithOutline = drgui_draw_round_rect_with_outline_dr_2d;
|
|
callbacks.drawText = drgui_draw_text_dr_2d;
|
|
callbacks.drawImage = drgui_draw_image_dr_2d;
|
|
|
|
callbacks.createFont = drgui_create_font_dr_2d;
|
|
callbacks.deleteFont = drgui_delete_font_dr_2d;
|
|
callbacks.getFontSize = drgui_get_font_size_dr_2d;
|
|
callbacks.getFontMetrics = drgui_get_font_metrics_dr_2d;
|
|
callbacks.getGlyphMetrics = drgui_get_glyph_metrics_dr_2d;
|
|
callbacks.measureString = drgui_measure_string_dr_2d;
|
|
|
|
callbacks.createImage = drgui_create_image_dr_2d;
|
|
callbacks.deleteImage = drgui_delete_image_dr_2d;
|
|
callbacks.getImageSize = drgui_get_image_size_dr_2d;
|
|
callbacks.getOptimalImageFormat = drgui_get_optimal_image_format_dr_2d;
|
|
callbacks.mapImageData = drgui_map_image_data_dr_2d;
|
|
callbacks.unmapImageData = drgui_unmap_image_data_dr_2d;
|
|
|
|
callbacks.getTextCursorPositionFromPoint = drgui_get_text_cursor_position_from_point_dr_2d;
|
|
callbacks.getTextCursorPositionFromChar = drgui_get_text_cursor_position_from_char_dr_2d;
|
|
|
|
drgui_register_painting_callbacks(pContext, pDrawingContext, callbacks);
|
|
}
|
|
|
|
|
|
void drgui_draw_begin_dr_2d(void* pPaintData)
|
|
{
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_begin_draw(pSurface);
|
|
}
|
|
|
|
void drgui_draw_end_dr_2d(void* pPaintData)
|
|
{
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_end_draw(pSurface);
|
|
}
|
|
|
|
void drgui_set_clip_dr_2d(drgui_rect rect, void* pPaintData)
|
|
{
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_set_clip(pSurface, rect.left, rect.top, rect.right, rect.bottom);
|
|
}
|
|
|
|
void drgui_get_clip_dr_2d(drgui_rect* pRectOut, void* pPaintData)
|
|
{
|
|
assert(pRectOut != NULL);
|
|
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_get_clip(pSurface, &pRectOut->left, &pRectOut->top, &pRectOut->right, &pRectOut->bottom);
|
|
}
|
|
|
|
void drgui_draw_rect_dr_2d(drgui_rect rect, drgui_color color, void* pPaintData)
|
|
{
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_draw_rect(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a));
|
|
}
|
|
|
|
void drgui_draw_rect_outline_dr_2d(drgui_rect rect, drgui_color color, float outlineWidth, void* pPaintData)
|
|
{
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_draw_rect_outline(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a), outlineWidth);
|
|
}
|
|
|
|
void drgui_draw_rect_with_outline_dr_2d(drgui_rect rect, drgui_color color, float outlineWidth, drgui_color outlineColor, void* pPaintData)
|
|
{
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_draw_rect_with_outline(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a), outlineWidth, dr2d_rgba(outlineColor.r, outlineColor.g, outlineColor.b, outlineColor.a));
|
|
}
|
|
|
|
void drgui_draw_round_rect_dr_2d(drgui_rect rect, drgui_color color, float radius, void* pPaintData)
|
|
{
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_draw_round_rect(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a), radius);
|
|
}
|
|
|
|
void drgui_draw_round_rect_outline_dr_2d(drgui_rect rect, drgui_color color, float radius, float outlineWidth, void* pPaintData)
|
|
{
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_draw_round_rect_outline(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a), radius, outlineWidth);
|
|
}
|
|
|
|
void drgui_draw_round_rect_with_outline_dr_2d(drgui_rect rect, drgui_color color, float radius, float outlineWidth, drgui_color outlineColor, void* pPaintData)
|
|
{
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_draw_round_rect_with_outline(pSurface, rect.left, rect.top, rect.right, rect.bottom, dr2d_rgba(color.r, color.g, color.b, color.a), radius, outlineWidth, dr2d_rgba(outlineColor.r, outlineColor.g, outlineColor.b, outlineColor.a));
|
|
}
|
|
|
|
void drgui_draw_text_dr_2d(drgui_resource font, const char* text, int textSizeInBytes, float posX, float posY, drgui_color color, drgui_color backgroundColor, void* pPaintData)
|
|
{
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_draw_text(pSurface, (dr2d_font*)font, text, textSizeInBytes, posX, posY, dr2d_rgba(color.r, color.g, color.b, color.a), dr2d_rgba(backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a));
|
|
}
|
|
|
|
void drgui_draw_image_dr_2d(drgui_resource image, drgui_draw_image_args* pArgs, void* pPaintData)
|
|
{
|
|
dr2d_surface* pSurface = (dr2d_surface*)pPaintData;
|
|
assert(pSurface != NULL);
|
|
|
|
dr2d_draw_image_args args;
|
|
args.dstX = pArgs->dstX;
|
|
args.dstY = pArgs->dstY;
|
|
args.dstWidth = pArgs->dstWidth;
|
|
args.dstHeight = pArgs->dstHeight;
|
|
args.srcX = pArgs->srcX;
|
|
args.srcY = pArgs->srcY;
|
|
args.srcWidth = pArgs->srcWidth;
|
|
args.srcHeight = pArgs->srcHeight;
|
|
args.foregroundTint = dr2d_rgba(pArgs->foregroundTint.r, pArgs->foregroundTint.g, pArgs->foregroundTint.b, pArgs->foregroundTint.a);
|
|
args.backgroundColor = dr2d_rgba(pArgs->backgroundColor.r, pArgs->backgroundColor.g, pArgs->backgroundColor.b, pArgs->backgroundColor.a);
|
|
args.options = pArgs->options;
|
|
dr2d_draw_image(pSurface, (dr2d_image*)image, &args);
|
|
}
|
|
|
|
|
|
drgui_resource drgui_create_font_dr_2d(void* pPaintingContext, const char* family, unsigned int size, drgui_font_weight weight, drgui_font_slant slant, float rotation, unsigned int flags)
|
|
{
|
|
return dr2d_create_font((dr2d_context*)pPaintingContext, family, size, (dr2d_font_weight)weight, (dr2d_font_slant)slant, rotation, flags);
|
|
}
|
|
|
|
void drgui_delete_font_dr_2d(drgui_resource font)
|
|
{
|
|
dr2d_delete_font((dr2d_font*)font);
|
|
}
|
|
|
|
unsigned int drgui_get_font_size_dr_2d(drgui_resource font)
|
|
{
|
|
return dr2d_get_font_size((dr2d_font*)font);
|
|
}
|
|
|
|
bool drgui_get_font_metrics_dr_2d(drgui_resource font, drgui_font_metrics* pMetricsOut)
|
|
{
|
|
assert(pMetricsOut != NULL);
|
|
|
|
dr2d_font_metrics metrics;
|
|
if (!dr2d_get_font_metrics((dr2d_font*)font, &metrics)) {
|
|
return false;
|
|
}
|
|
|
|
pMetricsOut->ascent = metrics.ascent;
|
|
pMetricsOut->descent = metrics.descent;
|
|
pMetricsOut->lineHeight = metrics.lineHeight;
|
|
pMetricsOut->spaceWidth = metrics.spaceWidth;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool drgui_get_glyph_metrics_dr_2d(drgui_resource font, unsigned int utf32, drgui_glyph_metrics* pMetricsOut)
|
|
{
|
|
assert(pMetricsOut != NULL);
|
|
|
|
dr2d_glyph_metrics metrics;
|
|
if (!dr2d_get_glyph_metrics((dr2d_font*)font, utf32, &metrics)) {
|
|
return false;
|
|
}
|
|
|
|
pMetricsOut->width = metrics.width;
|
|
pMetricsOut->height = metrics.height;
|
|
pMetricsOut->originX = metrics.originX;
|
|
pMetricsOut->originY = metrics.originY;
|
|
pMetricsOut->advanceX = metrics.advanceX;
|
|
pMetricsOut->advanceY = metrics.advanceY;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool drgui_measure_string_dr_2d(drgui_resource font, const char* text, size_t textSizeInBytes, float* pWidthOut, float* pHeightOut)
|
|
{
|
|
return dr2d_measure_string((dr2d_font*)font, text, textSizeInBytes, pWidthOut, pHeightOut);
|
|
}
|
|
|
|
bool drgui_get_text_cursor_position_from_point_dr_2d(drgui_resource font, const char* text, size_t textSizeInBytes, float maxWidth, float inputPosX, float* pTextCursorPosXOut, size_t* pCharacterIndexOut)
|
|
{
|
|
return dr2d_get_text_cursor_position_from_point((dr2d_font*)font, text, textSizeInBytes, maxWidth, inputPosX, pTextCursorPosXOut, pCharacterIndexOut);
|
|
}
|
|
|
|
bool drgui_get_text_cursor_position_from_char_dr_2d(drgui_resource font, const char* text, size_t characterIndex, float* pTextCursorPosXOut)
|
|
{
|
|
return dr2d_get_text_cursor_position_from_char((dr2d_font*)font, text, characterIndex, pTextCursorPosXOut);
|
|
}
|
|
|
|
|
|
drgui_resource drgui_create_image_dr_2d(void* pPaintingContext, unsigned int width, unsigned int height, drgui_image_format format, unsigned int stride, const void* pImageData)
|
|
{
|
|
dr2d_image_format dr2dFormat;
|
|
switch (format)
|
|
{
|
|
case drgui_image_format_bgra8: dr2dFormat = dr2d_image_format_bgra8; break;
|
|
case drgui_image_format_argb8: dr2dFormat = dr2d_image_format_argb8; break;
|
|
default: dr2dFormat = dr2d_image_format_rgba8;
|
|
}
|
|
|
|
return dr2d_create_image((dr2d_context*)pPaintingContext, width, height, dr2dFormat, stride, pImageData);
|
|
}
|
|
|
|
void drgui_delete_image_dr_2d(drgui_resource image)
|
|
{
|
|
dr2d_delete_image((dr2d_image*)image);
|
|
}
|
|
|
|
void drgui_get_image_size_dr_2d(drgui_resource image, unsigned int* pWidthOut, unsigned int* pHeightOut)
|
|
{
|
|
dr2d_get_image_size((dr2d_image*)image, pWidthOut, pHeightOut);
|
|
}
|
|
|
|
drgui_image_format drgui_get_optimal_image_format_dr_2d(void* pPaintingContext)
|
|
{
|
|
return (drgui_image_format)dr2d_get_optimal_image_format((dr2d_context*)pPaintingContext);
|
|
}
|
|
|
|
void* drgui_map_image_data_dr_2d(drgui_resource image, unsigned int accessFlags)
|
|
{
|
|
return dr2d_map_image_data((dr2d_image*)image, accessFlags);
|
|
}
|
|
|
|
void drgui_unmap_image_data_dr_2d(drgui_resource image)
|
|
{
|
|
dr2d_unmap_image_data((dr2d_image*)image);
|
|
}
|
|
|
|
#endif //DRGUI_NO_DR_2D
|
|
#endif //DR_GUI_IMPLEMENTATION
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Text Engine
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#ifndef DRGUI_NO_TEXT_EDITING
|
|
// QUICK NOTES
|
|
//
|
|
// - Text engines are used to make it easier to manage the layout of a block of text.
|
|
// - Text engines support basic editing which requires inbound events to be posted from the higher level application.
|
|
// - Text engines are not GUI elements. They are lower level objects that are used by higher level GUI elements.
|
|
// - Text engines normalize line endings to \n format. Keep this in mind when retrieving the text of a layout.
|
|
// - Text engine use the notion of a container which is used for determining which text runs are visible.
|
|
|
|
#ifndef drgui_text_engine_h
|
|
#define drgui_text_engine_h
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef struct drgui_text_engine drgui_text_engine;
|
|
|
|
typedef enum
|
|
{
|
|
drgui_text_engine_alignment_left,
|
|
drgui_text_engine_alignment_top,
|
|
drgui_text_engine_alignment_center,
|
|
drgui_text_engine_alignment_right,
|
|
drgui_text_engine_alignment_bottom,
|
|
} drgui_text_engine_alignment;
|
|
|
|
typedef struct
|
|
{
|
|
/// A pointer to the start of the string. This is NOT null terminated.
|
|
const char* text;
|
|
|
|
/// The length of the string, in bytes.
|
|
size_t textLength;
|
|
|
|
|
|
/// The font.
|
|
drgui_font* pFont;
|
|
|
|
/// The foreground color of the text.
|
|
drgui_color textColor;
|
|
|
|
/// The backgorund color of the text.
|
|
drgui_color backgroundColor;
|
|
|
|
|
|
/// The position to draw the text on the x axis.
|
|
float posX;
|
|
|
|
/// The position to draw the text on the y axis.
|
|
float posY;
|
|
|
|
/// The width of the run.
|
|
float width;
|
|
|
|
/// The height of the run.
|
|
float height;
|
|
|
|
|
|
// PROPERTIES BELOW ARE FOR INTERNAL USE ONLY
|
|
|
|
/// Index of the line the run is placed on. For runs that are new line characters, this will represent the number of lines that came before it. For
|
|
/// example, if this run represents the new-line character for the first line, this will be 0 and so on.
|
|
size_t iLine;
|
|
|
|
/// Index in the main text string of the first character of the run.
|
|
size_t iChar;
|
|
|
|
/// Index in the main text string of the character just past the last character in the run.
|
|
size_t iCharEnd;
|
|
|
|
} drgui_text_run;
|
|
|
|
typedef void (* drgui_text_engine_on_paint_text_proc) (drgui_text_engine* pTL, drgui_text_run* pRun, drgui_element* pElement, void* pPaintData);
|
|
typedef void (* drgui_text_engine_on_paint_rect_proc) (drgui_text_engine* pTL, drgui_rect rect, drgui_color color, drgui_element* pElement, void* pPaintData);
|
|
typedef void (* drgui_text_engine_on_cursor_move_proc) (drgui_text_engine* pTL);
|
|
typedef void (* drgui_text_engine_on_dirty_proc) (drgui_text_engine* pTL, drgui_rect rect);
|
|
typedef void (* drgui_text_engine_on_text_changed_proc) (drgui_text_engine* pTL);
|
|
typedef void (* drgui_text_engine_on_undo_point_changed_proc)(drgui_text_engine* pTL, unsigned int iUndoPoint);
|
|
|
|
|
|
/// Creates a new text engine object.
|
|
drgui_text_engine* drgui_create_text_engine(drgui_context* pContext, size_t extraDataSize, void* pExtraData);
|
|
|
|
/// Deletes the given text engine.
|
|
void drgui_delete_text_engine(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Retrieves the size of the extra data associated with the given text engine.
|
|
size_t drgui_text_engine_get_extra_data_size(drgui_text_engine* pTL);
|
|
|
|
/// Retrieves a pointer to the extra data associated with the given text engine.
|
|
void* drgui_text_engine_get_extra_data(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Sets the given text engine's text.
|
|
void drgui_text_engine_set_text(drgui_text_engine* pTL, const char* text);
|
|
|
|
/// Retrieves the given text engine's text.
|
|
///
|
|
/// @return The length of the string, not including the null terminator.
|
|
///
|
|
/// @remarks
|
|
/// Call this function with <textOut> set to NULL to retieve the required size of <textOut>.
|
|
size_t drgui_text_engine_get_text(drgui_text_engine* pTL, char* textOut, size_t textOutSize);
|
|
|
|
|
|
/// Sets the function to call when a region of the text engine needs to be redrawn.
|
|
void drgui_text_engine_set_on_dirty(drgui_text_engine* pTL, drgui_text_engine_on_dirty_proc proc);
|
|
|
|
/// Sets the function to call when the content of the given text engine has changed.
|
|
void drgui_text_engine_set_on_text_changed(drgui_text_engine* pTL, drgui_text_engine_on_text_changed_proc proc);
|
|
|
|
/// Sets the function to call when the content of the given text engine's current undo point has moved.
|
|
void drgui_text_engine_set_on_undo_point_changed(drgui_text_engine* pTL, drgui_text_engine_on_undo_point_changed_proc proc);
|
|
|
|
|
|
/// Sets the size of the container.
|
|
void drgui_text_engine_set_container_size(drgui_text_engine* pTL, float containerWidth, float containerHeight);
|
|
|
|
/// Retrieves the size of the container.
|
|
void drgui_text_engine_get_container_size(drgui_text_engine* pTL, float* pContainerWidthOut, float* pContainerHeightOut);
|
|
|
|
/// Retrieves the width of the container.
|
|
float drgui_text_engine_get_container_width(drgui_text_engine* pTL);
|
|
|
|
/// Retrieves the height of the container.
|
|
float drgui_text_engine_get_container_height(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Sets the inner offset of the given text engine.
|
|
void drgui_text_engine_set_inner_offset(drgui_text_engine* pTL, float innerOffsetX, float innerOffsetY);
|
|
|
|
/// Sets the inner offset of the given text engine on the x axis.
|
|
void drgui_text_engine_set_inner_offset_x(drgui_text_engine* pTL, float innerOffsetX);
|
|
|
|
/// Sets the inner offset of the given text engine on the y axis.
|
|
void drgui_text_engine_set_inner_offset_y(drgui_text_engine* pTL, float innerOffsetY);
|
|
|
|
/// Retrieves the inner offset of the given text engine.
|
|
void drgui_text_engine_get_inner_offset(drgui_text_engine* pTL, float* pInnerOffsetX, float* pInnerOffsetY);
|
|
|
|
/// Retrieves the inner offset of the given text engine on the x axis.
|
|
float drgui_text_engine_get_inner_offset_x(drgui_text_engine* pTL);
|
|
|
|
/// Retrieves the inner offset of the given text engine on the x axis.
|
|
float drgui_text_engine_get_inner_offset_y(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Sets the default font to use for text runs.
|
|
void drgui_text_engine_set_default_font(drgui_text_engine* pTL, drgui_font* pFont);
|
|
|
|
/// Retrieves the default font to use for text runs.
|
|
drgui_font* drgui_text_engine_get_default_font(drgui_text_engine* pTL);
|
|
|
|
/// Sets the default text color of the given text engine.
|
|
void drgui_text_engine_set_default_text_color(drgui_text_engine* pTL, drgui_color color);
|
|
|
|
/// Retrieves the default text color of the given text engine.
|
|
drgui_color drgui_text_engine_get_default_text_color(drgui_text_engine* pTL);
|
|
|
|
/// Sets the default background color of the given text engine.
|
|
void drgui_text_engine_set_default_bg_color(drgui_text_engine* pTL, drgui_color color);
|
|
|
|
/// Retrieves the default background color of the given text engine.
|
|
drgui_color drgui_text_engine_get_default_bg_color(drgui_text_engine* pTL);
|
|
|
|
/// Sets the background color of selected text.
|
|
void drgui_text_engine_set_selection_bg_color(drgui_text_engine* pTL, drgui_color color);
|
|
|
|
/// Retrieves the background color of selected text.
|
|
drgui_color drgui_text_engine_get_selection_bg_color(drgui_text_engine* pTL);
|
|
|
|
/// Sets the background color of the line the cursor is sitting on.
|
|
void drgui_text_engine_set_active_line_bg_color(drgui_text_engine* pTL, drgui_color color);
|
|
|
|
/// Retrieves the background color of the line the cursor is sitting on.
|
|
drgui_color drgui_text_engine_get_active_line_bg_color(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Sets the size of a tab in spaces.
|
|
void drgui_text_engine_set_tab_size(drgui_text_engine* pTL, unsigned int sizeInSpaces);
|
|
|
|
/// Retrieves the size of a tab in spaces.
|
|
unsigned int drgui_text_engine_get_tab_size(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Sets the horizontal alignment of the given text engine.
|
|
void drgui_text_engine_set_horizontal_align(drgui_text_engine* pTL, drgui_text_engine_alignment alignment);
|
|
|
|
/// Retrieves the horizontal aligment of the given text engine.
|
|
drgui_text_engine_alignment drgui_text_engine_get_horizontal_align(drgui_text_engine* pTL);
|
|
|
|
/// Sets the vertical alignment of the given text engine.
|
|
void drgui_text_engine_set_vertical_align(drgui_text_engine* pTL, drgui_text_engine_alignment alignment);
|
|
|
|
/// Retrieves the vertical aligment of the given text engine.
|
|
drgui_text_engine_alignment drgui_text_engine_get_vertical_align(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Retrieves the rectangle of the text relative to the bounds, taking alignment into account.
|
|
drgui_rect drgui_text_engine_get_text_rect_relative_to_bounds(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Sets the width of the text cursor.
|
|
void drgui_text_engine_set_cursor_width(drgui_text_engine* pTL, float cursorWidth);
|
|
|
|
/// Retrieves the width of the text cursor.
|
|
float drgui_text_engine_get_cursor_width(drgui_text_engine* pTL);
|
|
|
|
/// Sets the color of the text cursor.
|
|
void drgui_text_engine_set_cursor_color(drgui_text_engine* pTL, drgui_color cursorColor);
|
|
|
|
/// Retrieves the color of the text cursor.
|
|
drgui_color drgui_text_engine_get_cursor_color(drgui_text_engine* pTL);
|
|
|
|
/// Sets the blink rate of the cursor in milliseconds.
|
|
void drgui_text_engine_set_cursor_blink_rate(drgui_text_engine* pTL, unsigned int blinkRateInMilliseconds);
|
|
|
|
/// Retrieves the blink rate of the cursor in milliseconds.
|
|
unsigned int drgui_text_engine_get_cursor_blink_rate(drgui_text_engine* pTL);
|
|
|
|
/// Shows the cursor.
|
|
void drgui_text_engine_show_cursor(drgui_text_engine* pTL);
|
|
|
|
/// Hides the cursor.
|
|
void drgui_text_engine_hide_cursor(drgui_text_engine* pTL);
|
|
|
|
/// Determines whether or not the cursor is visible.
|
|
bool drgui_text_engine_is_showing_cursor(drgui_text_engine* pTL);
|
|
|
|
/// Retrieves the position of the cursor, relative to the container.
|
|
void drgui_text_engine_get_cursor_position(drgui_text_engine* pTL, float* pPosXOut, float* pPosYOut);
|
|
|
|
/// Retrieves the rectangle of the cursor, relative to the container.
|
|
drgui_rect drgui_text_engine_get_cursor_rect(drgui_text_engine* pTL);
|
|
|
|
/// Retrieves the index of the line the cursor is currently sitting on.
|
|
size_t drgui_text_engine_get_cursor_line(drgui_text_engine* pTL);
|
|
|
|
/// Retrieves the index of the column the cursor is currently sitting on.
|
|
size_t drgui_text_engine_get_cursor_column(drgui_text_engine* pTL);
|
|
|
|
/// Retrieves the index of the character the cursor is currently sitting on.
|
|
size_t drgui_text_engine_get_cursor_character(drgui_text_engine* pTL);
|
|
|
|
/// Moves the cursor to the closest character based on the given input position.
|
|
void drgui_text_engine_move_cursor_to_point(drgui_text_engine* pTL, float posX, float posY);
|
|
|
|
/// Moves the cursor of the given text engine to the left by one character.
|
|
bool drgui_text_engine_move_cursor_left(drgui_text_engine* pTL);
|
|
|
|
/// Moves the cursor of the given text engine to the right by one character.
|
|
bool drgui_text_engine_move_cursor_right(drgui_text_engine* pTL);
|
|
|
|
/// Moves the cursor of the given text engine up one line.
|
|
bool drgui_text_engine_move_cursor_up(drgui_text_engine* pTL);
|
|
|
|
/// Moves the cursor of the given text engine down one line.
|
|
bool drgui_text_engine_move_cursor_down(drgui_text_engine* pTL);
|
|
|
|
/// Moves the cursor up or down the given number of lines.
|
|
bool drgui_text_engine_move_cursor_y(drgui_text_engine* pTL, int amount);
|
|
|
|
/// Moves the cursor of the given text engine to the end of the line.
|
|
bool drgui_text_engine_move_cursor_to_end_of_line(drgui_text_engine* pTL);
|
|
|
|
/// Moves the cursor of the given text engine to the start of the line.
|
|
bool drgui_text_engine_move_cursor_to_start_of_line(drgui_text_engine* pTL);
|
|
|
|
/// Moves the cursor of the given text engine to the end of the line at the given index.
|
|
bool drgui_text_engine_move_cursor_to_end_of_line_by_index(drgui_text_engine* pTL, size_t iLine);
|
|
|
|
/// Moves the cursor of the given text engine to the start of the line at the given index.
|
|
bool drgui_text_engine_move_cursor_to_start_of_line_by_index(drgui_text_engine* pTL, size_t iLine);
|
|
|
|
/// Moves the cursor of the given text engine to the end of the text.
|
|
bool drgui_text_engine_move_cursor_to_end_of_text(drgui_text_engine* pTL);
|
|
|
|
/// Moves the cursor of the given text engine to the end of the text.
|
|
bool drgui_text_engine_move_cursor_to_start_of_text(drgui_text_engine* pTL);
|
|
|
|
/// Moves the cursor to the start of the selected text.
|
|
void drgui_text_engine_move_cursor_to_start_of_selection(drgui_text_engine* pTL);
|
|
|
|
/// Moves the cursor to the end of the selected text.
|
|
void drgui_text_engine_move_cursor_to_end_of_selection(drgui_text_engine* pTL);
|
|
|
|
/// Moves the cursor to the given character index.
|
|
void drgui_text_engine_move_cursor_to_character(drgui_text_engine* pTL, size_t characterIndex);
|
|
|
|
/// Determines whether or not the cursor is sitting at the start of the selection.
|
|
bool drgui_text_engine_is_cursor_at_start_of_selection(drgui_text_engine* pTL);
|
|
|
|
/// Determines whether or not the cursor is sitting at the end fo the selection.
|
|
bool drgui_text_engine_is_cursor_at_end_of_selection(drgui_text_engine* pTL);
|
|
|
|
/// Swaps the position of the cursor based on the current selection.
|
|
void drgui_text_engine_swap_selection_markers(drgui_text_engine* pTL);
|
|
|
|
/// Sets the function to call when the cursor in the given text engine is mvoed.
|
|
void drgui_text_engine_set_on_cursor_move(drgui_text_engine* pTL, drgui_text_engine_on_cursor_move_proc proc);
|
|
|
|
/// Refreshes the cursor and selection marker positions.
|
|
void drgui_text_engine_refresh_markers(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Inserts a character into the given text engine.
|
|
///
|
|
/// @return True if the text within the text engine has changed.
|
|
bool drgui_text_engine_insert_character(drgui_text_engine* pTL, unsigned int character, size_t insertIndex);
|
|
|
|
/// Inserts the given string at the given character index.
|
|
///
|
|
/// @return True if the text within the text engine has changed.
|
|
bool drgui_text_engine_insert_text(drgui_text_engine* pTL, const char* text, size_t insertIndex);
|
|
|
|
/// Deletes a range of text in the given text engine.
|
|
///
|
|
/// @return True if the text within the text engine has changed.
|
|
bool drgui_text_engine_delete_text_range(drgui_text_engine* pTL, size_t iFirstCh, size_t iLastChPlus1);
|
|
|
|
/// Inserts a character at the position of the cursor.
|
|
///
|
|
/// @return True if the text within the text engine has changed.
|
|
bool drgui_text_engine_insert_character_at_cursor(drgui_text_engine* pTL, unsigned int character);
|
|
|
|
/// Inserts a character at the position of the cursor.
|
|
///
|
|
/// @return True if the text within the text engine has changed.
|
|
bool drgui_text_engine_insert_text_at_cursor(drgui_text_engine* pTL, const char* text);
|
|
|
|
/// Deletes the character to the left of the cursor.
|
|
///
|
|
/// @return True if the text within the text engine has changed.
|
|
bool drgui_text_engine_delete_character_to_left_of_cursor(drgui_text_engine* pTL);
|
|
|
|
/// Deletes the character to the right of the cursor.
|
|
///
|
|
/// @return True if the text within the text engine has changed.
|
|
bool drgui_text_engine_delete_character_to_right_of_cursor(drgui_text_engine* pTL);
|
|
|
|
/// Deletes the currently selected text.
|
|
///
|
|
/// @return True if the text within the text engine has changed.
|
|
bool drgui_text_engine_delete_selected_text(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Enter's into selection mode.
|
|
///
|
|
/// @remarks
|
|
/// An application will typically enter selection mode when the Shift key is pressed, and then leave when the key is released.
|
|
/// @par
|
|
/// This will increment an internal counter, which is decremented with a corresponding call to drgui_text_engine_leave_selection_mode().
|
|
/// Selection mode will be enabled so long as this counter is greater than 0. Thus, you must ensure you cleanly leave selection
|
|
/// mode.
|
|
void drgui_text_engine_enter_selection_mode(drgui_text_engine* pTL);
|
|
|
|
/// Leaves selection mode.
|
|
///
|
|
/// @remarks
|
|
/// This decrements the internal counter. Selection mode will not be disabled while this reference counter is greater than 0. Always
|
|
/// ensure a leave is correctly matched with an enter.
|
|
void drgui_text_engine_leave_selection_mode(drgui_text_engine* pTL);
|
|
|
|
/// Determines whether or not the given text engine is in selection mode.
|
|
bool drgui_text_engine_is_in_selection_mode(drgui_text_engine* pTL);
|
|
|
|
/// Determines whether or not anything is selected in the given text engine.
|
|
bool drgui_text_engine_is_anything_selected(drgui_text_engine* pTL);
|
|
|
|
/// Deselects everything in the given text engine.
|
|
void drgui_text_engine_deselect_all(drgui_text_engine* pTL);
|
|
|
|
/// Selects everything in the given text engine.
|
|
void drgui_text_engine_select_all(drgui_text_engine* pTL);
|
|
|
|
/// Selects the given range of text.
|
|
void drgui_text_engine_select(drgui_text_engine* pTL, size_t firstCharacter, size_t lastCharacter);
|
|
|
|
/// Retrieves a copy of the selected text.
|
|
///
|
|
/// @remarks
|
|
/// This returns the length of the selected text. Call this once with <textOut> set to NULL to calculate the required size of the
|
|
/// buffer.
|
|
/// @par
|
|
/// If the output buffer is not larger enough, the string will be truncated.
|
|
size_t drgui_text_engine_get_selected_text(drgui_text_engine* pTL, char* textOut, size_t textOutLength);
|
|
|
|
/// Retrieves the index of the first line of the current selection.
|
|
size_t drgui_text_engine_get_selection_first_line(drgui_text_engine* pTL);
|
|
|
|
/// Retrieves the index of the last line of the current selection.
|
|
size_t drgui_text_engine_get_selection_last_line(drgui_text_engine* pTL);
|
|
|
|
/// Moves the selection anchor to the end of the given line.
|
|
void drgui_text_engine_move_selection_anchor_to_end_of_line(drgui_text_engine* pTL, size_t iLine);
|
|
|
|
/// Moves the selection anchor to the start of the given line.
|
|
void drgui_text_engine_move_selection_anchor_to_start_of_line(drgui_text_engine* pTL, size_t iLine);
|
|
|
|
/// Retrieves the line the selection anchor is sitting on.
|
|
size_t drgui_text_engine_get_selection_anchor_line(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Prepares the next undo/redo point.
|
|
///
|
|
/// @remarks
|
|
/// This captures the state that will be applied when the undo/redo point is undone.
|
|
bool drgui_text_engine_prepare_undo_point(drgui_text_engine* pTL);
|
|
|
|
/// Creates a snapshot of the current state of the text engine and pushes it to the top of the undo/redo stack.
|
|
bool drgui_text_engine_commit_undo_point(drgui_text_engine* pTL);
|
|
|
|
/// Performs an undo operation.
|
|
bool drgui_text_engine_undo(drgui_text_engine* pTL);
|
|
|
|
/// Performs a redo operation.
|
|
bool drgui_text_engine_redo(drgui_text_engine* pTL);
|
|
|
|
/// Retrieves the number of undo points remaining in the stack.
|
|
unsigned int drgui_text_engine_get_undo_points_remaining_count(drgui_text_engine* pTL);
|
|
|
|
/// Retrieves the number of redo points remaining in the stack.
|
|
unsigned int drgui_text_engine_get_redo_points_remaining_count(drgui_text_engine* pTL);
|
|
|
|
/// Clears the undo stack.
|
|
void drgui_text_engine_clear_undo_stack(drgui_text_engine* pTL);
|
|
|
|
|
|
|
|
/// Retrieves the number of lines in the given text engine.
|
|
size_t drgui_text_engine_get_line_count(drgui_text_engine* pTL);
|
|
|
|
/// Retrieves the number of lines that can fit on the visible portion of the layout, starting from the given line.
|
|
///
|
|
/// @remarks
|
|
/// Use this for controlling the page size for scrollbars.
|
|
size_t drgui_text_engine_get_visible_line_count_starting_at(drgui_text_engine* pTL, size_t iFirstLine);
|
|
|
|
/// Retrieves the position of the line at the given index on the y axis.
|
|
///
|
|
/// @remarks
|
|
/// Use this for calculating the inner offset for scrolling on the y axis.
|
|
float drgui_text_engine_get_line_pos_y(drgui_text_engine* pTL, size_t iLine);
|
|
|
|
/// Finds the line under the given point on the y axis relative to the container.
|
|
size_t drgui_text_engine_get_line_at_pos_y(drgui_text_engine* pTL, float posY);
|
|
|
|
/// Retrieves the index of the first character of the line at the given index.
|
|
size_t drgui_text_engine_get_line_first_character(drgui_text_engine* pTL, size_t iLine);
|
|
|
|
/// Retrieves the index of the last character of the line at the given index.
|
|
size_t drgui_text_engine_get_line_last_character(drgui_text_engine* pTL, size_t iLine);
|
|
|
|
/// Retrieves teh index of the first and last character of the line at the given index.
|
|
void drgui_text_engine_get_line_character_range(drgui_text_engine* pTL, size_t iLine, size_t* pCharStartOut, size_t* pCharEndOut);
|
|
|
|
|
|
/// Sets the function to call when a run of text needs to be painted for the given text engine.
|
|
void drgui_text_engine_set_on_paint_text(drgui_text_engine* pTL, drgui_text_engine_on_paint_text_proc proc);
|
|
|
|
/// Sets the function to call when a quad needs to the be painted for the given text engine.
|
|
void drgui_text_engine_set_on_paint_rect(drgui_text_engine* pTL, drgui_text_engine_on_paint_rect_proc proc);
|
|
|
|
/// Paints the given text engine by calling the appropriate painting callbacks.
|
|
///
|
|
/// @remarks
|
|
/// Typically a text engine will be painted to a GUI element. A pointer to an element can be passed to this function
|
|
/// which will be passed to the callback functions. This is purely for convenience and nothing is actually drawn to
|
|
/// the element outside of the callback functions.
|
|
void drgui_text_engine_paint(drgui_text_engine* pTL, drgui_rect rect, drgui_element* pElement, void* pPaintData);
|
|
|
|
|
|
/// Steps the given text engine by the given number of milliseconds.
|
|
///
|
|
/// @remarks
|
|
/// This will trigger the on_dirty callback when the cursor switches it's blink states.
|
|
void drgui_text_engine_step(drgui_text_engine* pTL, unsigned int milliseconds);
|
|
|
|
|
|
/// Calls the given painting callbacks for the line numbers of the given text engine.
|
|
void drgui_text_engine_paint_line_numbers(drgui_text_engine* pTL, float lineNumbersWidth, float lineNumbersHeight, drgui_color textColor, drgui_color backgroundColor, drgui_text_engine_on_paint_text_proc onPaintText, drgui_text_engine_on_paint_rect_proc onPaintRect, drgui_element* pElement, void* pPaintData);
|
|
|
|
|
|
/// Finds the given string starting from the cursor and then looping back.
|
|
bool drgui_text_engine_find_next(drgui_text_engine* pTL, const char* text, size_t* pSelectionStartOut, size_t* pSelectionEndOut);
|
|
|
|
/// Finds the given string starting from the cursor, but does not loop back.
|
|
bool drgui_text_engine_find_next_no_loop(drgui_text_engine* pTL, const char* text, size_t* pSelectionStartOut, size_t* pSelectionEndOut);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif //drgui_text_engine_h
|
|
|
|
|
|
#ifdef DR_GUI_IMPLEMENTATION
|
|
typedef struct
|
|
{
|
|
/// The index of the run within the line the marker is positioned on.
|
|
size_t iRun;
|
|
|
|
/// The index of the character within the run the marker is positioned to the left of.
|
|
size_t iChar;
|
|
|
|
/// The position on the x axis, relative to the x position of the run.
|
|
float relativePosX;
|
|
|
|
/// The absolute position on the x axis to place the marker when moving up and down lines. Note that this is not relative
|
|
/// to the run, but rather the line. This will be updated when the marker is moved left and right.
|
|
float absoluteSickyPosX;
|
|
|
|
} drgui_text_marker;
|
|
|
|
/// Keeps track of the current state of the text engine. Used for calculating the difference between two states for undo/redo.
|
|
typedef struct
|
|
{
|
|
/// The text. Can be null in some cases where it isn't used.
|
|
char* text;
|
|
|
|
/// The index of the character the cursor is positioned at.
|
|
size_t cursorPos;
|
|
|
|
/// The index of the character the selection anchor is positioned at.
|
|
size_t selectionAnchorPos;
|
|
|
|
/// Whether or not anything is selected.
|
|
bool isAnythingSelected;
|
|
|
|
} drgui_text_engine_state;
|
|
|
|
typedef struct
|
|
{
|
|
/// The position in the main string where the change is located. The length of the relevant string is used to determines how
|
|
/// large of a chunk of text needs to be replaced.
|
|
size_t diffPos;
|
|
|
|
/// The string that was replaced. On undo, this will be inserted into the text engine. Can be empty, in which case this state
|
|
/// object was created in response to an insert operation.
|
|
char* oldText;
|
|
|
|
/// The string that replaces the old text. On redo, this will be inserted into the text engine. This can be empty, in which case
|
|
/// this state object was created in response to a delete operation.
|
|
char* newText;
|
|
|
|
/// The state of the text engine at the time the undo point was prepared, not including the text. The <text> attribute
|
|
/// of this object is always null.
|
|
drgui_text_engine_state oldState;
|
|
|
|
/// The state of the text engine at the time the undo point was committed, not including the text. The <text> attribute
|
|
/// of this object is always null.
|
|
drgui_text_engine_state newState;
|
|
|
|
} drgui_text_engine_undo_state;
|
|
|
|
struct drgui_text_engine
|
|
{
|
|
/// The main text of the layout.
|
|
char* text;
|
|
|
|
/// The length of the text.
|
|
size_t textLength;
|
|
|
|
|
|
/// The function to call when the text engine needs to be redrawn.
|
|
drgui_text_engine_on_dirty_proc onDirty;
|
|
|
|
/// The function to call when the content of the text engine changes.
|
|
drgui_text_engine_on_text_changed_proc onTextChanged;
|
|
|
|
/// The function to call when the current undo point has changed.
|
|
drgui_text_engine_on_undo_point_changed_proc onUndoPointChanged;
|
|
|
|
|
|
/// The width of the container.
|
|
float containerWidth;
|
|
|
|
/// The height of the container.
|
|
float containerHeight;
|
|
|
|
/// The inner offset of the container.
|
|
float innerOffsetX;
|
|
|
|
/// The inner offset of the container.
|
|
float innerOffsetY;
|
|
|
|
|
|
/// The default font.
|
|
drgui_font* pDefaultFont;
|
|
|
|
/// The default text color.
|
|
drgui_color defaultTextColor;
|
|
|
|
/// The default background color.
|
|
drgui_color defaultBackgroundColor;
|
|
|
|
/// The background color to use for selected text.
|
|
drgui_color selectionBackgroundColor;
|
|
|
|
/// The background color to use for the line the cursor is currently sitting on.
|
|
drgui_color lineBackgroundColor;
|
|
|
|
/// The size of a tab in spaces.
|
|
unsigned int tabSizeInSpaces;
|
|
|
|
/// The horizontal alignment.
|
|
drgui_text_engine_alignment horzAlign;
|
|
|
|
/// The vertical alignment.
|
|
drgui_text_engine_alignment vertAlign;
|
|
|
|
/// The width of the text cursor.
|
|
float cursorWidth;
|
|
|
|
/// The color of the text cursor.
|
|
drgui_color cursorColor;
|
|
|
|
/// The blink rate in milliseconds of the cursor.
|
|
unsigned int cursorBlinkRate;
|
|
|
|
/// The amount of time in milliseconds to toggle the cursor's blink state.
|
|
unsigned int timeToNextCursorBlink;
|
|
|
|
/// Whether or not the cursor is showing based on it's blinking state.
|
|
bool isCursorBlinkOn;
|
|
|
|
/// Whether or not the cursor is being shown. False by default.
|
|
bool isShowingCursor;
|
|
|
|
|
|
/// The total width of the text.
|
|
float textBoundsWidth;
|
|
|
|
/// The total height of the text.
|
|
float textBoundsHeight;
|
|
|
|
|
|
/// The cursor.
|
|
drgui_text_marker cursor;
|
|
|
|
/// The selection anchor.
|
|
drgui_text_marker selectionAnchor;
|
|
|
|
|
|
/// The selection mode counter. When this is greater than 0 we are in selection mode, otherwise we are not. This
|
|
/// is incremented by enter_selection_mode() and decremented by leave_selection_mode().
|
|
unsigned int selectionModeCounter;
|
|
|
|
/// Whether or not anything is selected.
|
|
bool isAnythingSelected;
|
|
|
|
|
|
/// The function to call when a text run needs to be painted.
|
|
drgui_text_engine_on_paint_text_proc onPaintText;
|
|
|
|
/// The function to call when a rectangle needs to be painted.
|
|
drgui_text_engine_on_paint_rect_proc onPaintRect;
|
|
|
|
/// The function to call when the cursor moves.
|
|
drgui_text_engine_on_cursor_move_proc onCursorMove;
|
|
|
|
|
|
/// The prepared undo/redo state. This will be filled with some state by PrepareUndoRedoPoint() and again with CreateUndoRedoPoint().
|
|
drgui_text_engine_state preparedState;
|
|
|
|
/// The undo/redo stack.
|
|
drgui_text_engine_undo_state* pUndoStack;
|
|
|
|
/// The number of items in the undo/redo stack.
|
|
unsigned int undoStackCount;
|
|
|
|
/// The index of the undo/redo state item we are currently sitting on.
|
|
unsigned int iUndoState;
|
|
|
|
|
|
/// The counter used to determine when an onDirty event needs to be posted.
|
|
unsigned int dirtyCounter;
|
|
|
|
/// The accumulated dirty rectangle. When dirtyCounter hits 0, this is the rectangle that's posted to the onDirty callback.
|
|
drgui_rect accumulatedDirtyRect;
|
|
|
|
|
|
/// A pointer to the buffer containing details about every run in the layout.
|
|
drgui_text_run* pRuns;
|
|
|
|
/// The number of runs in <pRuns>.
|
|
size_t runCount;
|
|
|
|
/// The size of the <pRuns> buffer in drgui_text_run's. This is used to determine whether or not the buffer
|
|
/// needs to be reallocated upon adding a new run.
|
|
size_t runBufferSize;
|
|
|
|
|
|
/// The size of the extra data.
|
|
size_t extraDataSize;
|
|
|
|
/// A pointer to the extra data.
|
|
char pExtraData[1];
|
|
};
|
|
|
|
/// Structure containing information about a line. This is used by first_line() and next_line().
|
|
typedef struct
|
|
{
|
|
/// The index of the line.
|
|
size_t index;
|
|
|
|
/// The position of the line on the y axis.
|
|
float posY;
|
|
|
|
/// The height of the line.
|
|
float height;
|
|
|
|
/// The index of the first run on the line.
|
|
size_t iFirstRun;
|
|
|
|
/// The index of the last run on the line.
|
|
size_t iLastRun;
|
|
|
|
} drgui_text_engine_line;
|
|
|
|
|
|
/// Performs a complete refresh of the given text engine.
|
|
///
|
|
/// @remarks
|
|
/// This will delete every run and re-create them.
|
|
DRGUI_PRIVATE void drgui_text_engine__refresh(drgui_text_engine* pTL);
|
|
|
|
/// Refreshes the alignment of the given text engine.
|
|
DRGUI_PRIVATE void drgui_text_engine__refresh_alignment(drgui_text_engine* pTL);
|
|
|
|
/// Appends a text run to the list of runs in the given text engine.
|
|
DRGUI_PRIVATE void drgui_text_engine__push_text_run(drgui_text_engine* pTL, drgui_text_run* pRun);
|
|
|
|
/// Clears the internal list of text runs.
|
|
DRGUI_PRIVATE void drgui_text_engine__clear_text_runs(drgui_text_engine* pTL);
|
|
|
|
/// Helper for calculating the offset to apply to each line based on the alignment of the given text engine.
|
|
DRGUI_PRIVATE void drgui_text_engine__calculate_line_alignment_offset(drgui_text_engine* pTL, float lineWidth, float* pOffsetXOut, float* pOffsetYOut);
|
|
|
|
/// Helper for determine whether or not the given text run is whitespace.
|
|
DRGUI_PRIVATE bool drgui_text_engine__is_text_run_whitespace(drgui_text_engine* pTL, drgui_text_run* pRun);
|
|
|
|
/// Helper for calculating the width of a tab.
|
|
DRGUI_PRIVATE float drgui_text_engine__get_tab_width(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Finds the line that's closest to the given point relative to the text.
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_closest_line_to_point(drgui_text_engine* pTL, float inputPosYRelativeToText, size_t* pFirstRunIndexOnLineOut, size_t* pLastRunIndexOnLinePlus1Out);
|
|
|
|
/// Finds the run that's closest to the given point relative to the text.
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_closest_run_to_point(drgui_text_engine* pTL, float inputPosXRelativeToText, float inputPosYRelativeToText, size_t* pRunIndexOut);
|
|
|
|
/// Retrieves some basic information about a line, namely the index of the last run on the line, and the line's height.
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_line_info(drgui_text_engine* pTL, size_t iFirstRunOnLine, size_t* pLastRunIndexOnLinePlus1Out, float* pLineHeightOut);
|
|
|
|
/// Retrieves some basic information about a line by it's index.
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_line_info_by_index(drgui_text_engine* pTL, size_t iLine, drgui_rect* pRectOut, size_t* pFirstRunIndexOut, size_t* pLastRunIndexPlus1Out);
|
|
|
|
/// Finds the last run on the line that the given run is sitting on.
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_last_run_on_line_starting_from_run(drgui_text_engine* pTL, size_t iRun, size_t* pLastRunIndexOnLineOut);
|
|
|
|
/// Finds the first run on the line that the given run is sitting on.
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_first_run_on_line_starting_from_run(drgui_text_engine* pTL, size_t iRun, size_t* pFirstRunIndexOnLineOut);
|
|
|
|
/// Finds the run containing the character at the given index.
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_run_at_character(drgui_text_engine* pTL, size_t iChar, size_t* pRunIndexOut);
|
|
|
|
|
|
/// Creates a blank text marker.
|
|
DRGUI_PRIVATE drgui_text_marker drgui_text_engine__new_marker();
|
|
|
|
/// Moves the given text marker to the given point, relative to the container.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_point_relative_to_container(drgui_text_engine* pTL, drgui_text_marker* pMarker, float inputPosX, float inputPosY);
|
|
|
|
/// Retrieves the position of the given text marker relative to the container.
|
|
DRGUI_PRIVATE void drgui_text_engine__get_marker_position_relative_to_container(drgui_text_engine* pTL, drgui_text_marker* pMarker, float* pPosXOut, float* pPosYOut);
|
|
|
|
/// Moves the marker to the given point, relative to the text rectangle.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_point(drgui_text_engine* pTL, drgui_text_marker* pMarker, float inputPosXRelativeToText, float inputPosYRelativeToText);
|
|
|
|
/// Moves the given marker to the left by one character.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_left(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
/// Moves the given marker to the right by one character.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_right(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
/// Moves the given marker up one line.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_up(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
/// Moves the given marker down one line.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_down(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
/// Moves the given marker down one line.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_y(drgui_text_engine* pTL, drgui_text_marker* pMarker, int amount);
|
|
|
|
/// Moves the given marker to the end of the line it's currently sitting on.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_line(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
/// Moves the given marker to the start of the line it's currently sitting on.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_line(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
/// Moves the given marker to the end of the line at the given index.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_line_by_index(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iLine);
|
|
|
|
/// Moves the given marker to the start of the line at the given index.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_line_by_index(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iLine);
|
|
|
|
/// Moves the given marker to the end of the text.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_text(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
/// Moves the given marker to the start of the text.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_text(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
/// Moves the given marker to the last character of the given run.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_last_character_of_run(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iRun);
|
|
|
|
/// Moves the given marker to the first character of the given run.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_first_character_of_run(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iRun);
|
|
|
|
/// Moves the given marker to the last character of the previous run.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_last_character_of_prev_run(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
/// Moves the given marker to the first character of the next run.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_first_character_of_next_run(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
/// Moves the given marker to the character at the given position.
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_character(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iChar);
|
|
|
|
|
|
/// Updates the relative position of the given marker.
|
|
///
|
|
/// @remarks
|
|
/// This assumes the iRun and iChar properties are valid.
|
|
DRGUI_PRIVATE bool drgui_text_engine__update_marker_relative_position(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
/// Updates the sticky position of the given marker.
|
|
DRGUI_PRIVATE void drgui_text_engine__update_marker_sticky_position(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
|
|
/// Retrieves the index of the character the given marker is located at.
|
|
DRGUI_PRIVATE size_t drgui_text_engine__get_marker_absolute_char_index(drgui_text_engine* pTL, drgui_text_marker* pMarker);
|
|
|
|
|
|
/// Helper function for determining whether or not there is any spacing between the selection markers.
|
|
DRGUI_PRIVATE bool drgui_text_engine__has_spacing_between_selection_markers(drgui_text_engine* pTL);
|
|
|
|
/// Splits the given run into sub-runs based on the current selection rectangle. Returns the sub-run count.
|
|
DRGUI_PRIVATE size_t drgui_text_engine__split_text_run_by_selection(drgui_text_engine* pTL, drgui_text_run* pRunToSplit, drgui_text_run pSubRunsOut[3]);
|
|
|
|
|
|
/// Retrieves pointers to the selection markers in the correct order.
|
|
DRGUI_PRIVATE bool drgui_text_engine__get_selection_markers(drgui_text_engine* pTL, drgui_text_marker** ppSelectionMarker0Out, drgui_text_marker** ppSelectionMarker1Out);
|
|
|
|
|
|
/// Retrieves an iterator to the first line in the text engine.
|
|
DRGUI_PRIVATE bool drgui_text_engine__first_line(drgui_text_engine* pTL, drgui_text_engine_line* pLine);
|
|
|
|
/// Retrieves an iterator to the next line in the text engine.
|
|
DRGUI_PRIVATE bool drgui_text_engine__next_line(drgui_text_engine* pTL, drgui_text_engine_line* pLine);
|
|
|
|
|
|
/// Removes the undo/redo state stack items after the current undo/redo point.
|
|
DRGUI_PRIVATE void drgui_text_engine__trim_undo_stack(drgui_text_engine* pTL);
|
|
|
|
/// Initializes the given undo state object by diff-ing the given layout states.
|
|
DRGUI_PRIVATE bool drgui_text_engine__diff_states(drgui_text_engine_state* pPrevState, drgui_text_engine_state* pCurrentState, drgui_text_engine_undo_state* pUndoStateOut);
|
|
|
|
/// Uninitializes the given undo state object. This basically just free's the internal string.
|
|
DRGUI_PRIVATE void drgui_text_engine__uninit_undo_state(drgui_text_engine_undo_state* pUndoState);
|
|
|
|
/// Pushes an undo state onto the undo stack.
|
|
DRGUI_PRIVATE void drgui_text_engine__push_undo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState);
|
|
|
|
/// Applies the given undo state.
|
|
DRGUI_PRIVATE void drgui_text_engine__apply_undo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState);
|
|
|
|
/// Applies the given undo state as a redo operation.
|
|
DRGUI_PRIVATE void drgui_text_engine__apply_redo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState);
|
|
|
|
|
|
/// Retrieves a rectangle relative to the given text engine that's equal to the size of the container.
|
|
DRGUI_PRIVATE drgui_rect drgui_text_engine__local_rect(drgui_text_engine* pTL);
|
|
|
|
|
|
/// Called when the cursor moves.
|
|
DRGUI_PRIVATE void drgui_text_engine__on_cursor_move(drgui_text_engine* pTL);
|
|
|
|
/// Called when the text engine needs to be redrawn.
|
|
DRGUI_PRIVATE void drgui_text_engine__on_dirty(drgui_text_engine* pTL, drgui_rect rect);
|
|
|
|
/// Increments the counter. The counter is decremented with drgui_text_engine__end_dirty(). Use this for batching redraws.
|
|
DRGUI_PRIVATE void drgui_text_engine__begin_dirty(drgui_text_engine* pTL);
|
|
|
|
/// Decrements the dirty counter, and if it hits 0 posts the onDirty callback.
|
|
DRGUI_PRIVATE void drgui_text_engine__end_dirty(drgui_text_engine* pTL);
|
|
|
|
|
|
|
|
|
|
drgui_text_engine* drgui_create_text_engine(drgui_context* pContext, size_t extraDataSize, void* pExtraData)
|
|
{
|
|
if (pContext == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_text_engine* pTL = (drgui_text_engine*)malloc(sizeof(drgui_text_engine) + extraDataSize);
|
|
if (pTL == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
pTL->text = NULL;
|
|
pTL->textLength = 0;
|
|
pTL->onDirty = NULL;
|
|
pTL->onTextChanged = NULL;
|
|
pTL->onUndoPointChanged = NULL;
|
|
pTL->containerWidth = 0;
|
|
pTL->containerHeight = 0;
|
|
pTL->innerOffsetX = 0;
|
|
pTL->innerOffsetY = 0;
|
|
pTL->pDefaultFont = NULL;
|
|
pTL->defaultTextColor = drgui_rgb(224, 224, 224);
|
|
pTL->defaultBackgroundColor = drgui_rgb(48, 48, 48);
|
|
pTL->selectionBackgroundColor = drgui_rgb(64, 128, 192);
|
|
pTL->lineBackgroundColor = drgui_rgb(40, 40, 40);
|
|
pTL->tabSizeInSpaces = 4;
|
|
pTL->horzAlign = drgui_text_engine_alignment_left;
|
|
pTL->vertAlign = drgui_text_engine_alignment_top;
|
|
pTL->cursorWidth = 1;
|
|
pTL->cursorColor = drgui_rgb(224, 224, 224);
|
|
pTL->cursorBlinkRate = 500;
|
|
pTL->timeToNextCursorBlink = pTL->cursorBlinkRate;
|
|
pTL->isCursorBlinkOn = true;
|
|
pTL->isShowingCursor = false;
|
|
pTL->textBoundsWidth = 0;
|
|
pTL->textBoundsHeight = 0;
|
|
pTL->cursor = drgui_text_engine__new_marker();
|
|
pTL->selectionAnchor = drgui_text_engine__new_marker();
|
|
pTL->selectionModeCounter = 0;
|
|
pTL->isAnythingSelected = false;
|
|
pTL->onPaintText = NULL;
|
|
pTL->onPaintRect = NULL;
|
|
pTL->onCursorMove = NULL;
|
|
pTL->preparedState.text = NULL;
|
|
pTL->pUndoStack = NULL;
|
|
pTL->undoStackCount = 0;
|
|
pTL->iUndoState = 0;
|
|
pTL->dirtyCounter = 0;
|
|
pTL->accumulatedDirtyRect = drgui_make_inside_out_rect();
|
|
pTL->pRuns = NULL;
|
|
pTL->runCount = 0;
|
|
pTL->runBufferSize = 0;
|
|
|
|
pTL->extraDataSize = extraDataSize;
|
|
if (pExtraData != NULL) {
|
|
memcpy(pTL->pExtraData, pExtraData, extraDataSize);
|
|
}
|
|
|
|
return pTL;
|
|
}
|
|
|
|
void drgui_delete_text_engine(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_clear_undo_stack(pTL);
|
|
|
|
free(pTL->pRuns);
|
|
free(pTL->preparedState.text);
|
|
free(pTL->text);
|
|
free(pTL);
|
|
}
|
|
|
|
|
|
size_t drgui_text_engine_get_extra_data_size(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->extraDataSize;
|
|
}
|
|
|
|
void* drgui_text_engine_get_extra_data(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTL->pExtraData;
|
|
}
|
|
|
|
|
|
void drgui_text_engine_set_text(drgui_text_engine* pTL, const char* text)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
size_t textLength = strlen(text);
|
|
|
|
free(pTL->text);
|
|
pTL->text = (char*)malloc(textLength + 1); // +1 for null terminator.
|
|
|
|
// We now need to copy over the text, however we need to skip past \r characters in order to normalize line endings
|
|
// and keep everything simple.
|
|
char* dst = pTL->text;
|
|
const char* src = text;
|
|
while (*src != '\0')
|
|
{
|
|
if (*src != '\r') {
|
|
*dst++ = *src;
|
|
}
|
|
|
|
src++;
|
|
}
|
|
*dst = '\0';
|
|
|
|
pTL->textLength = dst - pTL->text;
|
|
|
|
// A change in text means we need to refresh the layout.
|
|
drgui_text_engine__refresh(pTL);
|
|
|
|
// If the position of the cursor is past the last character we'll need to move it.
|
|
if (drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor) >= pTL->textLength) {
|
|
drgui_text_engine_move_cursor_to_end_of_text(pTL);
|
|
}
|
|
|
|
if (pTL->onTextChanged) {
|
|
pTL->onTextChanged(pTL);
|
|
}
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
size_t drgui_text_engine_get_text(drgui_text_engine* pTL, char* textOut, size_t textOutSize)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (textOut == NULL) {
|
|
return pTL->textLength;
|
|
}
|
|
|
|
|
|
if (drgui__strcpy_s(textOut, textOutSize, (pTL->text != NULL) ? pTL->text : "") == 0) {
|
|
return pTL->textLength;
|
|
}
|
|
|
|
return 0; // Error with strcpy_s().
|
|
}
|
|
|
|
|
|
void drgui_text_engine_set_on_dirty(drgui_text_engine* pTL, drgui_text_engine_on_dirty_proc proc)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->onDirty = proc;
|
|
}
|
|
|
|
void drgui_text_engine_set_on_text_changed(drgui_text_engine* pTL, drgui_text_engine_on_text_changed_proc proc)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->onTextChanged = proc;
|
|
}
|
|
|
|
void drgui_text_engine_set_on_undo_point_changed(drgui_text_engine* pTL, drgui_text_engine_on_undo_point_changed_proc proc)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->onUndoPointChanged = proc;
|
|
}
|
|
|
|
|
|
void drgui_text_engine_set_container_size(drgui_text_engine* pTL, float containerWidth, float containerHeight)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->containerWidth = containerWidth;
|
|
pTL->containerHeight = containerHeight;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
void drgui_text_engine_get_container_size(drgui_text_engine* pTL, float* pContainerWidthOut, float* pContainerHeightOut)
|
|
{
|
|
float containerWidth = 0;
|
|
float containerHeight = 0;
|
|
|
|
if (pTL != NULL)
|
|
{
|
|
containerWidth = pTL->containerWidth;
|
|
containerHeight = pTL->containerHeight;
|
|
}
|
|
|
|
|
|
if (pContainerWidthOut) {
|
|
*pContainerWidthOut = containerWidth;
|
|
}
|
|
if (pContainerHeightOut) {
|
|
*pContainerHeightOut = containerHeight;
|
|
}
|
|
}
|
|
|
|
float drgui_text_engine_get_container_width(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->containerWidth;
|
|
}
|
|
|
|
float drgui_text_engine_get_container_height(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->containerHeight;
|
|
}
|
|
|
|
|
|
void drgui_text_engine_set_inner_offset(drgui_text_engine* pTL, float innerOffsetX, float innerOffsetY)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->innerOffsetX = innerOffsetX;
|
|
pTL->innerOffsetY = innerOffsetY;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
void drgui_text_engine_set_inner_offset_x(drgui_text_engine* pTL, float innerOffsetX)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->innerOffsetX = innerOffsetX;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
void drgui_text_engine_set_inner_offset_y(drgui_text_engine* pTL, float innerOffsetY)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->innerOffsetY = innerOffsetY;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
void drgui_text_engine_get_inner_offset(drgui_text_engine* pTL, float* pInnerOffsetX, float* pInnerOffsetY)
|
|
{
|
|
float innerOffsetX = 0;
|
|
float innerOffsetY = 0;
|
|
|
|
if (pTL != NULL)
|
|
{
|
|
innerOffsetX = pTL->innerOffsetX;
|
|
innerOffsetY = pTL->innerOffsetY;
|
|
}
|
|
|
|
|
|
if (pInnerOffsetX) {
|
|
*pInnerOffsetX = innerOffsetX;
|
|
}
|
|
if (pInnerOffsetY) {
|
|
*pInnerOffsetY = innerOffsetY;
|
|
}
|
|
}
|
|
|
|
float drgui_text_engine_get_inner_offset_x(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->innerOffsetX;
|
|
}
|
|
|
|
float drgui_text_engine_get_inner_offset_y(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->innerOffsetY;
|
|
}
|
|
|
|
|
|
void drgui_text_engine_set_default_font(drgui_text_engine* pTL, drgui_font* pFont)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->pDefaultFont = pFont;
|
|
|
|
// A change in font requires a layout refresh.
|
|
drgui_text_engine__refresh(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
drgui_font* drgui_text_engine_get_default_font(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTL->pDefaultFont;
|
|
}
|
|
|
|
void drgui_text_engine_set_default_text_color(drgui_text_engine* pTL, drgui_color color)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->defaultTextColor = color;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
drgui_color drgui_text_engine_get_default_text_color(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTL->defaultTextColor;
|
|
}
|
|
|
|
void drgui_text_engine_set_default_bg_color(drgui_text_engine* pTL, drgui_color color)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->defaultBackgroundColor = color;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
drgui_color drgui_text_engine_get_default_bg_color(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTL->defaultBackgroundColor;
|
|
}
|
|
|
|
void drgui_text_engine_set_selection_bg_color(drgui_text_engine* pTL, drgui_color color)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->selectionBackgroundColor = color;
|
|
|
|
if (drgui_text_engine_is_anything_selected(pTL)) {
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
}
|
|
|
|
drgui_color drgui_text_engine_get_selection_bg_color(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTL->selectionBackgroundColor;
|
|
}
|
|
|
|
void drgui_text_engine_set_active_line_bg_color(drgui_text_engine* pTL, drgui_color color)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->lineBackgroundColor = color;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
drgui_color drgui_text_engine_get_active_line_bg_color(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTL->lineBackgroundColor;
|
|
}
|
|
|
|
|
|
void drgui_text_engine_set_tab_size(drgui_text_engine* pTL, unsigned int sizeInSpaces)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTL->tabSizeInSpaces != sizeInSpaces)
|
|
{
|
|
pTL->tabSizeInSpaces = sizeInSpaces;
|
|
|
|
drgui_text_engine__refresh(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
}
|
|
|
|
unsigned int drgui_text_engine_get_tab_size(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->tabSizeInSpaces;
|
|
}
|
|
|
|
|
|
void drgui_text_engine_set_horizontal_align(drgui_text_engine* pTL, drgui_text_engine_alignment alignment)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTL->horzAlign != alignment)
|
|
{
|
|
pTL->horzAlign = alignment;
|
|
|
|
drgui_text_engine__refresh_alignment(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
}
|
|
|
|
drgui_text_engine_alignment drgui_text_engine_get_horizontal_align(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return drgui_text_engine_alignment_left;
|
|
}
|
|
|
|
return pTL->horzAlign;
|
|
}
|
|
|
|
void drgui_text_engine_set_vertical_align(drgui_text_engine* pTL, drgui_text_engine_alignment alignment)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTL->vertAlign != alignment)
|
|
{
|
|
pTL->vertAlign = alignment;
|
|
|
|
drgui_text_engine__refresh_alignment(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
}
|
|
|
|
drgui_text_engine_alignment drgui_text_engine_get_vertical_align(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return drgui_text_engine_alignment_top;
|
|
}
|
|
|
|
return pTL->vertAlign;
|
|
}
|
|
|
|
|
|
drgui_rect drgui_text_engine_get_text_rect_relative_to_bounds(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return drgui_make_rect(0, 0, 0, 0);
|
|
}
|
|
|
|
drgui_rect rect;
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
|
|
|
|
switch (pTL->horzAlign)
|
|
{
|
|
case drgui_text_engine_alignment_right:
|
|
{
|
|
rect.left = pTL->containerWidth - pTL->textBoundsWidth;
|
|
break;
|
|
}
|
|
|
|
case drgui_text_engine_alignment_center:
|
|
{
|
|
rect.left = (pTL->containerWidth - pTL->textBoundsWidth) / 2;
|
|
break;
|
|
}
|
|
|
|
case drgui_text_engine_alignment_left:
|
|
case drgui_text_engine_alignment_top: // Invalid for horizontal align.
|
|
case drgui_text_engine_alignment_bottom: // Invalid for horizontal align.
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
switch (pTL->vertAlign)
|
|
{
|
|
case drgui_text_engine_alignment_bottom:
|
|
{
|
|
rect.top = pTL->containerHeight - pTL->textBoundsHeight;
|
|
break;
|
|
}
|
|
|
|
case drgui_text_engine_alignment_center:
|
|
{
|
|
rect.top = (pTL->containerHeight - pTL->textBoundsHeight) / 2;
|
|
break;
|
|
}
|
|
|
|
case drgui_text_engine_alignment_top:
|
|
case drgui_text_engine_alignment_left: // Invalid for vertical align.
|
|
case drgui_text_engine_alignment_right: // Invalid for vertical align.
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
rect.left += pTL->innerOffsetX;
|
|
rect.top += pTL->innerOffsetY;
|
|
rect.right = rect.left + pTL->textBoundsWidth;
|
|
rect.bottom = rect.top + pTL->textBoundsHeight;
|
|
|
|
return rect;
|
|
}
|
|
|
|
|
|
void drgui_text_engine_set_cursor_width(drgui_text_engine* pTL, float cursorWidth)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_rect oldCursorRect = drgui_text_engine_get_cursor_rect(pTL);
|
|
pTL->cursorWidth = cursorWidth;
|
|
if (pTL->cursorWidth > 0 && pTL->cursorWidth < 1) {
|
|
pTL->cursorWidth = 1;
|
|
}
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_rect_union(oldCursorRect, drgui_text_engine_get_cursor_rect(pTL)));
|
|
}
|
|
|
|
float drgui_text_engine_get_cursor_width(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->cursorWidth;
|
|
}
|
|
|
|
void drgui_text_engine_set_cursor_color(drgui_text_engine* pTL, drgui_color cursorColor)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->cursorColor = cursorColor;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine_get_cursor_rect(pTL));
|
|
}
|
|
|
|
drgui_color drgui_text_engine_get_cursor_color(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTL->cursorColor;
|
|
}
|
|
|
|
void drgui_text_engine_set_cursor_blink_rate(drgui_text_engine* pTL, unsigned int blinkRateInMilliseconds)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->cursorBlinkRate = blinkRateInMilliseconds;
|
|
}
|
|
|
|
unsigned int drgui_text_engine_get_cursor_blink_rate(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->cursorBlinkRate;
|
|
}
|
|
|
|
void drgui_text_engine_show_cursor(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (!pTL->isShowingCursor)
|
|
{
|
|
pTL->isShowingCursor = true;
|
|
|
|
pTL->timeToNextCursorBlink = pTL->cursorBlinkRate;
|
|
pTL->isCursorBlinkOn = true;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine_get_cursor_rect(pTL));
|
|
}
|
|
}
|
|
|
|
void drgui_text_engine_hide_cursor(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTL->isShowingCursor)
|
|
{
|
|
pTL->isShowingCursor = false;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine_get_cursor_rect(pTL));
|
|
}
|
|
}
|
|
|
|
bool drgui_text_engine_is_showing_cursor(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pTL->isShowingCursor;
|
|
}
|
|
|
|
void drgui_text_engine_get_cursor_position(drgui_text_engine* pTL, float* pPosXOut, float* pPosYOut)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine__get_marker_position_relative_to_container(pTL, &pTL->cursor, pPosXOut, pPosYOut);
|
|
}
|
|
|
|
drgui_rect drgui_text_engine_get_cursor_rect(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return drgui_make_rect(0, 0, 0, 0);
|
|
}
|
|
|
|
drgui_rect lineRect = drgui_make_rect(0, 0, 0, 0);
|
|
|
|
if (pTL->runCount > 0)
|
|
{
|
|
drgui_text_engine__find_line_info_by_index(pTL, pTL->pRuns[pTL->cursor.iRun].iLine, &lineRect, NULL, NULL);
|
|
}
|
|
else if (pTL->pDefaultFont != NULL)
|
|
{
|
|
drgui_font_metrics defaultFontMetrics;
|
|
drgui_get_font_metrics(pTL->pDefaultFont, &defaultFontMetrics);
|
|
|
|
lineRect.bottom = (float)defaultFontMetrics.lineHeight;
|
|
}
|
|
|
|
|
|
|
|
float cursorPosX;
|
|
float cursorPosY;
|
|
drgui_text_engine_get_cursor_position(pTL, &cursorPosX, &cursorPosY);
|
|
|
|
return drgui_make_rect(cursorPosX, cursorPosY, cursorPosX + pTL->cursorWidth, cursorPosY + (lineRect.bottom - lineRect.top));
|
|
}
|
|
|
|
size_t drgui_text_engine_get_cursor_line(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->pRuns[pTL->cursor.iRun].iLine;
|
|
}
|
|
|
|
size_t drgui_text_engine_get_cursor_column(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
float posX;
|
|
float posY;
|
|
drgui_text_engine_get_cursor_position(pTL, &posX, &posY);
|
|
|
|
drgui_font_metrics fontMetrics;
|
|
drgui_get_font_metrics(pTL->pDefaultFont, &fontMetrics);
|
|
|
|
return (unsigned int)((int)posX / fontMetrics.spaceWidth);
|
|
}
|
|
|
|
size_t drgui_text_engine_get_cursor_character(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor);
|
|
}
|
|
|
|
void drgui_text_engine_move_cursor_to_point(drgui_text_engine* pTL, float posX, float posY)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
drgui_text_engine__move_marker_to_point_relative_to_container(pTL, &pTL->cursor, posX, posY);
|
|
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
}
|
|
|
|
bool drgui_text_engine_move_cursor_left(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_left(pTL, &pTL->cursor)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_move_cursor_right(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_right(pTL, &pTL->cursor)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_move_cursor_up(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_up(pTL, &pTL->cursor)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_move_cursor_down(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_down(pTL, &pTL->cursor)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_move_cursor_y(drgui_text_engine* pTL, int amount)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_y(pTL, &pTL->cursor, amount)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_move_cursor_to_end_of_line(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_to_end_of_line(pTL, &pTL->cursor)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_move_cursor_to_start_of_line(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_to_start_of_line(pTL, &pTL->cursor)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_move_cursor_to_end_of_line_by_index(drgui_text_engine* pTL, size_t iLine)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_to_end_of_line_by_index(pTL, &pTL->cursor, iLine)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_move_cursor_to_start_of_line_by_index(drgui_text_engine* pTL, size_t iLine)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_to_start_of_line_by_index(pTL, &pTL->cursor, iLine)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_move_cursor_to_end_of_text(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_to_end_of_text(pTL, &pTL->cursor)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_move_cursor_to_start_of_text(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_to_start_of_text(pTL, &pTL->cursor)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void drgui_text_engine_move_cursor_to_start_of_selection(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_marker* pSelectionMarker0;
|
|
drgui_text_marker* pSelectionMarker1;
|
|
if (drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1))
|
|
{
|
|
pTL->cursor = *pSelectionMarker0;
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
}
|
|
|
|
void drgui_text_engine_move_cursor_to_end_of_selection(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_marker* pSelectionMarker0;
|
|
drgui_text_marker* pSelectionMarker1;
|
|
if (drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1))
|
|
{
|
|
pTL->cursor = *pSelectionMarker1;
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
}
|
|
|
|
void drgui_text_engine_move_cursor_to_character(drgui_text_engine* pTL, size_t characterIndex)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
if (drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, characterIndex)) {
|
|
if (drgui_text_engine_is_in_selection_mode(pTL)) {
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool drgui_text_engine_is_cursor_at_start_of_selection(drgui_text_engine* pTL)
|
|
{
|
|
drgui_text_marker* pSelectionMarker0;
|
|
drgui_text_marker* pSelectionMarker1;
|
|
if (drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) {
|
|
return &pTL->cursor == pSelectionMarker0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_is_cursor_at_end_of_selection(drgui_text_engine* pTL)
|
|
{
|
|
drgui_text_marker* pSelectionMarker0;
|
|
drgui_text_marker* pSelectionMarker1;
|
|
if (drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) {
|
|
return &pTL->cursor == pSelectionMarker1;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void drgui_text_engine_swap_selection_markers(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_marker* pSelectionMarker0;
|
|
drgui_text_marker* pSelectionMarker1;
|
|
if (drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1))
|
|
{
|
|
size_t iRunOld = pTL->cursor.iRun;
|
|
size_t iCharOld = pTL->cursor.iChar;
|
|
|
|
drgui_text_marker temp = *pSelectionMarker0;
|
|
*pSelectionMarker0 = *pSelectionMarker1;
|
|
*pSelectionMarker1 = temp;
|
|
|
|
if (iRunOld != pTL->cursor.iRun || iCharOld != pTL->cursor.iChar) {
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_text_engine_set_on_cursor_move(drgui_text_engine* pTL, drgui_text_engine_on_cursor_move_proc proc)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->onCursorMove = proc;
|
|
}
|
|
|
|
void drgui_text_engine_refresh_markers(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL || pTL->pRuns == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Cursor.
|
|
drgui_text_run* pRun = pTL->pRuns + pTL->cursor.iRun;
|
|
drgui_get_text_cursor_position_from_char(pRun->pFont, pTL->text + pRun->iChar, pTL->cursor.iChar, OUT &pTL->cursor.relativePosX);
|
|
|
|
pRun = pTL->pRuns + pTL->selectionAnchor.iRun;
|
|
drgui_get_text_cursor_position_from_char(pRun->pFont, pTL->text + pRun->iChar, pTL->selectionAnchor.iChar, OUT &pTL->selectionAnchor.relativePosX);
|
|
}
|
|
|
|
bool drgui_text_engine_insert_character(drgui_text_engine* pTL, unsigned int character, size_t insertIndex)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Transform '\r' to '\n'.
|
|
if (character == '\r') {
|
|
character = '\n';
|
|
}
|
|
|
|
|
|
// TODO: Add proper support for UTF-8.
|
|
char* pOldText = pTL->text;
|
|
char* pNewText = (char*)malloc(pTL->textLength + 1 + 1); // +1 for the new character and +1 for the null terminator.
|
|
|
|
if (insertIndex > 0) {
|
|
memcpy(pNewText, pOldText, insertIndex);
|
|
}
|
|
|
|
pNewText[insertIndex] = (char)character;
|
|
|
|
if (insertIndex < pTL->textLength) {
|
|
memcpy(pNewText + insertIndex + 1, pOldText + insertIndex, pTL->textLength - insertIndex);
|
|
}
|
|
|
|
pTL->textLength += 1;
|
|
pTL->text = pNewText;
|
|
pNewText[pTL->textLength] = '\0';
|
|
|
|
free(pOldText);
|
|
|
|
|
|
|
|
|
|
// The layout will have changed so it needs to be refreshed.
|
|
drgui_text_engine__refresh(pTL);
|
|
|
|
if (pTL->onTextChanged) {
|
|
pTL->onTextChanged(pTL);
|
|
}
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool drgui_text_engine_insert_text(drgui_text_engine* pTL, const char* text, size_t insertIndex)
|
|
{
|
|
if (pTL == NULL || text == NULL) {
|
|
return false;;
|
|
}
|
|
|
|
size_t newTextLength = strlen(text);
|
|
if (newTextLength == 0) {
|
|
return false;
|
|
}
|
|
|
|
|
|
// TODO: Add proper support for UTF-8.
|
|
char* pOldText = pTL->text;
|
|
char* pNewText = (char*)malloc(pTL->textLength + newTextLength + 1); // +1 for the new character and +1 for the null terminator.
|
|
|
|
if (insertIndex > 0) {
|
|
memcpy(pNewText, pOldText, insertIndex);
|
|
}
|
|
|
|
|
|
// Replace \r\n with \n.
|
|
{
|
|
char* dst = pNewText + insertIndex;
|
|
const char* src = text;
|
|
size_t srcLen = newTextLength;
|
|
while (*src != '\0' && srcLen > 0)
|
|
{
|
|
if (*src != '\r') {
|
|
*dst++ = *src;
|
|
}
|
|
|
|
src++;
|
|
srcLen -= 1;
|
|
}
|
|
|
|
newTextLength = dst - (pNewText + insertIndex);
|
|
}
|
|
|
|
if (insertIndex < pTL->textLength) {
|
|
memcpy(pNewText + insertIndex + newTextLength, pOldText + insertIndex, pTL->textLength - insertIndex);
|
|
}
|
|
|
|
pTL->textLength += newTextLength;
|
|
pTL->text = pNewText;
|
|
pNewText[pTL->textLength] = '\0';
|
|
|
|
free(pOldText);
|
|
|
|
|
|
// The layout will have changed so it needs to be refreshed.
|
|
drgui_text_engine__refresh(pTL);
|
|
|
|
if (pTL->onTextChanged) {
|
|
pTL->onTextChanged(pTL);
|
|
}
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool drgui_text_engine_delete_text_range(drgui_text_engine* pTL, size_t iFirstCh, size_t iLastChPlus1)
|
|
{
|
|
if (pTL == NULL || iLastChPlus1 == iFirstCh) {
|
|
return false;
|
|
}
|
|
|
|
if (iFirstCh > iLastChPlus1) {
|
|
size_t temp = iFirstCh;
|
|
iFirstCh = iLastChPlus1;
|
|
iLastChPlus1 = temp;
|
|
}
|
|
|
|
|
|
size_t bytesToRemove = iLastChPlus1 - iFirstCh;
|
|
if (bytesToRemove > 0)
|
|
{
|
|
memmove(pTL->text + iFirstCh, pTL->text + iLastChPlus1, pTL->textLength - iLastChPlus1);
|
|
pTL->textLength -= bytesToRemove;
|
|
pTL->text[pTL->textLength] = '\0';
|
|
|
|
// The layout will have changed.
|
|
drgui_text_engine__refresh(pTL);
|
|
|
|
if (pTL->onTextChanged) {
|
|
pTL->onTextChanged(pTL);
|
|
}
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_insert_character_at_cursor(drgui_text_engine* pTL, unsigned int character)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iAbsoluteMarkerChar = 0;
|
|
|
|
drgui_text_run* pRun = pTL->pRuns + pTL->cursor.iRun;
|
|
if (pTL->runCount > 0 && pRun != NULL) {
|
|
iAbsoluteMarkerChar = pRun->iChar + pTL->cursor.iChar;
|
|
}
|
|
|
|
drgui_text_engine__begin_dirty(pTL);
|
|
{
|
|
drgui_text_engine_insert_character(pTL, character, iAbsoluteMarkerChar);
|
|
drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, iAbsoluteMarkerChar + 1);
|
|
}
|
|
drgui_text_engine__end_dirty(pTL);
|
|
|
|
|
|
// The cursor's sticky position needs to be updated whenever the text is edited.
|
|
drgui_text_engine__update_marker_sticky_position(pTL, &pTL->cursor);
|
|
|
|
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool drgui_text_engine_insert_text_at_cursor(drgui_text_engine* pTL, const char* text)
|
|
{
|
|
if (pTL == NULL || text == NULL) {
|
|
return false;
|
|
}
|
|
|
|
drgui_text_engine__begin_dirty(pTL);
|
|
{
|
|
size_t cursorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor);
|
|
drgui_text_engine_insert_text(pTL, text, cursorPos);
|
|
drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, cursorPos + strlen(text));
|
|
}
|
|
drgui_text_engine__end_dirty(pTL);
|
|
|
|
|
|
// The cursor's sticky position needs to be updated whenever the text is edited.
|
|
drgui_text_engine__update_marker_sticky_position(pTL, &pTL->cursor);
|
|
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool drgui_text_engine_delete_character_to_left_of_cursor(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// We just move the cursor to the left, and then delete the character to the right.
|
|
if (drgui_text_engine_move_cursor_left(pTL)) {
|
|
drgui_text_engine_delete_character_to_right_of_cursor(pTL);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_delete_character_to_right_of_cursor(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
drgui_text_run* pRun = pTL->pRuns + pTL->cursor.iRun;
|
|
size_t iAbsoluteMarkerChar = pRun->iChar + pTL->cursor.iChar;
|
|
|
|
if (iAbsoluteMarkerChar < pTL->textLength)
|
|
{
|
|
// TODO: Add proper support for UTF-8.
|
|
memmove(pTL->text + iAbsoluteMarkerChar, pTL->text + iAbsoluteMarkerChar + 1, pTL->textLength - iAbsoluteMarkerChar);
|
|
pTL->textLength -= 1;
|
|
pTL->text[pTL->textLength] = '\0';
|
|
|
|
|
|
|
|
// The layout will have changed.
|
|
drgui_text_engine__refresh(pTL);
|
|
drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, iAbsoluteMarkerChar);
|
|
|
|
if (pTL->onTextChanged) {
|
|
pTL->onTextChanged(pTL);
|
|
}
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_delete_selected_text(drgui_text_engine* pTL)
|
|
{
|
|
// Don't do anything if nothing is selected.
|
|
if (!drgui_text_engine_is_anything_selected(pTL)) {
|
|
return false;
|
|
}
|
|
|
|
drgui_text_marker* pSelectionMarker0 = &pTL->selectionAnchor;
|
|
drgui_text_marker* pSelectionMarker1 = &pTL->cursor;
|
|
if (pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar > pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar)
|
|
{
|
|
drgui_text_marker* temp = pSelectionMarker0;
|
|
pSelectionMarker0 = pSelectionMarker1;
|
|
pSelectionMarker1 = temp;
|
|
}
|
|
|
|
size_t iSelectionChar0 = pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar;
|
|
size_t iSelectionChar1 = pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar;
|
|
|
|
drgui_text_engine__begin_dirty(pTL);
|
|
bool wasTextChanged = drgui_text_engine_delete_text_range(pTL, iSelectionChar0, iSelectionChar1);
|
|
if (wasTextChanged)
|
|
{
|
|
// The marker needs to be updated based on the new layout.
|
|
drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, iSelectionChar0);
|
|
|
|
// The cursor's sticky position also needs to be updated.
|
|
drgui_text_engine__update_marker_sticky_position(pTL, &pTL->cursor);
|
|
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
|
|
|
|
// Reset the selection marker.
|
|
pTL->selectionAnchor = pTL->cursor;
|
|
pTL->isAnythingSelected = false;
|
|
}
|
|
|
|
drgui_text_engine__end_dirty(pTL);
|
|
return wasTextChanged;
|
|
}
|
|
|
|
|
|
void drgui_text_engine_enter_selection_mode(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
// If we've just entered selection mode and nothing is currently selected, we want to set the selection anchor to the current cursor position.
|
|
if (!drgui_text_engine_is_in_selection_mode(pTL) && !pTL->isAnythingSelected) {
|
|
pTL->selectionAnchor = pTL->cursor;
|
|
}
|
|
|
|
pTL->selectionModeCounter += 1;
|
|
}
|
|
|
|
void drgui_text_engine_leave_selection_mode(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTL->selectionModeCounter > 0) {
|
|
pTL->selectionModeCounter -= 1;
|
|
}
|
|
}
|
|
|
|
bool drgui_text_engine_is_in_selection_mode(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pTL->selectionModeCounter > 0;
|
|
}
|
|
|
|
bool drgui_text_engine_is_anything_selected(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pTL->isAnythingSelected;
|
|
}
|
|
|
|
void drgui_text_engine_deselect_all(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->isAnythingSelected = false;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
void drgui_text_engine_select_all(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine__move_marker_to_start_of_text(pTL, &pTL->selectionAnchor);
|
|
drgui_text_engine__move_marker_to_end_of_text(pTL, &pTL->cursor);
|
|
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
void drgui_text_engine_select(drgui_text_engine* pTL, size_t firstCharacter, size_t lastCharacter)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine__move_marker_to_character(pTL, &pTL->selectionAnchor, firstCharacter);
|
|
drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, lastCharacter);
|
|
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
|
|
size_t drgui_text_engine_get_selected_text(drgui_text_engine* pTL, char* textOut, size_t textOutSize)
|
|
{
|
|
if (pTL == NULL || (textOut != NULL && textOutSize == 0)) {
|
|
return 0;
|
|
}
|
|
|
|
if (!drgui_text_engine_is_anything_selected(pTL)) {
|
|
return 0;
|
|
}
|
|
|
|
|
|
drgui_text_marker* pSelectionMarker0;
|
|
drgui_text_marker* pSelectionMarker1;
|
|
if (!drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) {
|
|
return false;
|
|
}
|
|
|
|
size_t iSelectionChar0 = pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar;
|
|
size_t iSelectionChar1 = pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar;
|
|
|
|
size_t selectedTextLength = iSelectionChar1 - iSelectionChar0;
|
|
|
|
if (textOut != NULL) {
|
|
drgui__strncpy_s(textOut, textOutSize, pTL->text + iSelectionChar0, selectedTextLength);
|
|
}
|
|
|
|
return selectedTextLength;
|
|
}
|
|
|
|
size_t drgui_text_engine_get_selection_first_line(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
drgui_text_marker* pSelectionMarker0;
|
|
drgui_text_marker* pSelectionMarker1;
|
|
if (!drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->pRuns[pSelectionMarker0->iRun].iLine;
|
|
}
|
|
|
|
size_t drgui_text_engine_get_selection_last_line(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
drgui_text_marker* pSelectionMarker0;
|
|
drgui_text_marker* pSelectionMarker1;
|
|
if (!drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->pRuns[pSelectionMarker1->iRun].iLine;
|
|
}
|
|
|
|
void drgui_text_engine_move_selection_anchor_to_end_of_line(drgui_text_engine* pTL, size_t iLine)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine__move_marker_to_end_of_line_by_index(pTL, &pTL->selectionAnchor, iLine);
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
void drgui_text_engine_move_selection_anchor_to_start_of_line(drgui_text_engine* pTL, size_t iLine)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine__move_marker_to_start_of_line_by_index(pTL, &pTL->selectionAnchor, iLine);
|
|
pTL->isAnythingSelected = drgui_text_engine__has_spacing_between_selection_markers(pTL);
|
|
}
|
|
|
|
size_t drgui_text_engine_get_selection_anchor_line(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->pRuns[pTL->selectionAnchor.iRun].iLine;
|
|
}
|
|
|
|
|
|
|
|
bool drgui_text_engine_prepare_undo_point(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// If we have a previously prepared state we'll need to clear it.
|
|
if (pTL->preparedState.text != NULL) {
|
|
free(pTL->preparedState.text);
|
|
}
|
|
|
|
pTL->preparedState.text = (char*)malloc(pTL->textLength + 1);
|
|
drgui__strcpy_s(pTL->preparedState.text, pTL->textLength + 1, (pTL->text != NULL) ? pTL->text : "");
|
|
|
|
pTL->preparedState.cursorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor);
|
|
pTL->preparedState.selectionAnchorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->selectionAnchor);
|
|
pTL->preparedState.isAnythingSelected = pTL->isAnythingSelected;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool drgui_text_engine_commit_undo_point(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// The undo point must have been prepared earlier.
|
|
if (pTL->preparedState.text == NULL) {
|
|
return false;
|
|
}
|
|
|
|
|
|
// The undo state is creating by diff-ing the prepared state and the current state.
|
|
drgui_text_engine_state currentState;
|
|
currentState.text = pTL->text;
|
|
currentState.cursorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor);
|
|
currentState.selectionAnchorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->selectionAnchor);
|
|
currentState.isAnythingSelected = pTL->isAnythingSelected;
|
|
|
|
drgui_text_engine_undo_state undoState;
|
|
if (!drgui_text_engine__diff_states(&pTL->preparedState, ¤tState, &undoState)) {
|
|
return false;
|
|
}
|
|
|
|
|
|
// At this point we have the undo state ready and we just need to add it the undo stack. Before doing so, however,
|
|
// we need to trim the end fo the stack.
|
|
drgui_text_engine__trim_undo_stack(pTL);
|
|
drgui_text_engine__push_undo_state(pTL, &undoState);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool drgui_text_engine_undo(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL || pTL->pUndoStack == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (drgui_text_engine_get_undo_points_remaining_count(pTL) > 0)
|
|
{
|
|
drgui_text_engine_undo_state* pUndoState = pTL->pUndoStack + (pTL->iUndoState - 1);
|
|
assert(pUndoState != NULL);
|
|
|
|
drgui_text_engine__apply_undo_state(pTL, pUndoState);
|
|
pTL->iUndoState -= 1;
|
|
|
|
if (pTL->onUndoPointChanged) {
|
|
pTL->onUndoPointChanged(pTL, pTL->iUndoState);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_text_engine_redo(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL || pTL->pUndoStack == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (drgui_text_engine_get_redo_points_remaining_count(pTL) > 0)
|
|
{
|
|
drgui_text_engine_undo_state* pUndoState = pTL->pUndoStack + pTL->iUndoState;
|
|
assert(pUndoState != NULL);
|
|
|
|
drgui_text_engine__apply_redo_state(pTL, pUndoState);
|
|
pTL->iUndoState += 1;
|
|
|
|
if (pTL->onUndoPointChanged) {
|
|
pTL->onUndoPointChanged(pTL, pTL->iUndoState);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
unsigned int drgui_text_engine_get_undo_points_remaining_count(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->iUndoState;
|
|
}
|
|
|
|
unsigned int drgui_text_engine_get_redo_points_remaining_count(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (pTL->undoStackCount > 0)
|
|
{
|
|
assert(pTL->iUndoState <= pTL->undoStackCount);
|
|
return pTL->undoStackCount - pTL->iUndoState;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void drgui_text_engine_clear_undo_stack(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL || pTL->pUndoStack == NULL) {
|
|
return;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < pTL->undoStackCount; ++i) {
|
|
drgui_text_engine__uninit_undo_state(pTL->pUndoStack + i);
|
|
}
|
|
|
|
free(pTL->pUndoStack);
|
|
|
|
pTL->pUndoStack = NULL;
|
|
pTL->undoStackCount = 0;
|
|
|
|
if (pTL->iUndoState > 0) {
|
|
pTL->iUndoState = 0;
|
|
|
|
if (pTL->onUndoPointChanged) {
|
|
pTL->onUndoPointChanged(pTL, pTL->iUndoState);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
size_t drgui_text_engine_get_line_count(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->pRuns[pTL->runCount - 1].iLine + 1;
|
|
}
|
|
|
|
size_t drgui_text_engine_get_visible_line_count_starting_at(drgui_text_engine* pTL, size_t iFirstLine)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
unsigned int count = 0;
|
|
float lastLineBottom = 0;
|
|
|
|
// First thing we do is find the first line.
|
|
unsigned int iLine = 0;
|
|
drgui_text_engine_line line;
|
|
if (drgui_text_engine__first_line(pTL, &line))
|
|
{
|
|
do
|
|
{
|
|
if (iLine >= iFirstLine) {
|
|
break;
|
|
}
|
|
|
|
iLine += 1;
|
|
} while (drgui_text_engine__next_line(pTL, &line));
|
|
|
|
|
|
// At this point we are at the first line and we need to start counting.
|
|
do
|
|
{
|
|
if (line.posY + pTL->innerOffsetY >= pTL->containerHeight) {
|
|
break;
|
|
}
|
|
|
|
count += 1;
|
|
lastLineBottom = line.posY + line.height;
|
|
|
|
} while (drgui_text_engine__next_line(pTL, &line));
|
|
}
|
|
|
|
|
|
// At this point there may be some empty space below the last line, in which case we use the line height of the default font to fill
|
|
// out the remaining space.
|
|
if (lastLineBottom + pTL->innerOffsetY < pTL->containerHeight)
|
|
{
|
|
drgui_font_metrics defaultFontMetrics;
|
|
if (drgui_get_font_metrics(pTL->pDefaultFont, &defaultFontMetrics)) {
|
|
count += (unsigned int)((pTL->containerHeight - (lastLineBottom + pTL->innerOffsetY)) / defaultFontMetrics.lineHeight);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (count == 0) {
|
|
return 1;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
float drgui_text_engine_get_line_pos_y(drgui_text_engine* pTL, size_t iLine)
|
|
{
|
|
drgui_rect lineRect;
|
|
if (!drgui_text_engine__find_line_info_by_index(pTL, iLine, &lineRect, NULL, NULL)) {
|
|
return 0;
|
|
}
|
|
|
|
return lineRect.top;
|
|
}
|
|
|
|
size_t drgui_text_engine_get_line_at_pos_y(drgui_text_engine* pTL, float posY)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTL);
|
|
|
|
size_t iRun;
|
|
|
|
float inputPosYRelativeToText = posY - textRect.top;
|
|
if (!drgui_text_engine__find_closest_run_to_point(pTL, 0, inputPosYRelativeToText, &iRun)) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->pRuns[iRun].iLine;
|
|
}
|
|
|
|
size_t drgui_text_engine_get_line_first_character(drgui_text_engine* pTL, size_t iLine)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
size_t firstRunIndex0;
|
|
size_t lastRunIndexPlus1;
|
|
if (drgui_text_engine__find_line_info_by_index(pTL, iLine, NULL, &firstRunIndex0, &lastRunIndexPlus1)) {
|
|
return pTL->pRuns[firstRunIndex0].iChar;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t drgui_text_engine_get_line_last_character(drgui_text_engine* pTL, size_t iLine)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
size_t firstRunIndex0;
|
|
size_t lastRunIndexPlus1;
|
|
if (drgui_text_engine__find_line_info_by_index(pTL, iLine, NULL, &firstRunIndex0, &lastRunIndexPlus1)) {
|
|
size_t charEnd = pTL->pRuns[lastRunIndexPlus1 - 1].iCharEnd;
|
|
if (charEnd > 0) {
|
|
charEnd -= 1;
|
|
}
|
|
|
|
return charEnd;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void drgui_text_engine_get_line_character_range(drgui_text_engine* pTL, size_t iLine, size_t* pCharStartOut, size_t* pCharEndOut)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return;
|
|
}
|
|
|
|
size_t charStart = 0;
|
|
size_t charEnd = 0;
|
|
|
|
size_t firstRunIndex0;
|
|
size_t lastRunIndexPlus1;
|
|
if (drgui_text_engine__find_line_info_by_index(pTL, iLine, NULL, &firstRunIndex0, &lastRunIndexPlus1)) {
|
|
charStart = pTL->pRuns[firstRunIndex0].iChar;
|
|
charEnd = pTL->pRuns[lastRunIndexPlus1 - 1].iCharEnd;
|
|
if (charEnd > 0) {
|
|
charEnd -= 1;
|
|
}
|
|
}
|
|
|
|
if (pCharStartOut) {
|
|
*pCharStartOut = charStart;
|
|
}
|
|
if (pCharEndOut) {
|
|
*pCharEndOut = charEnd;
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_text_engine_set_on_paint_text(drgui_text_engine* pTL, drgui_text_engine_on_paint_text_proc proc)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->onPaintText = proc;
|
|
}
|
|
|
|
void drgui_text_engine_set_on_paint_rect(drgui_text_engine* pTL, drgui_text_engine_on_paint_rect_proc proc)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->onPaintRect = proc;
|
|
}
|
|
|
|
void drgui_text_engine_paint(drgui_text_engine* pTL, drgui_rect rect, drgui_element* pElement, void* pPaintData)
|
|
{
|
|
if (pTL == NULL || pTL->onPaintText == NULL || pTL->onPaintRect == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (rect.left < 0) {
|
|
rect.left = 0;
|
|
}
|
|
if (rect.top < 0) {
|
|
rect.top = 0;
|
|
}
|
|
if (rect.right > pTL->containerWidth) {
|
|
rect.right = pTL->containerWidth;
|
|
}
|
|
if (rect.bottom > pTL->containerHeight) {
|
|
rect.bottom = pTL->containerHeight;
|
|
}
|
|
|
|
if (rect.right <= rect.left || rect.bottom <= rect.top) {
|
|
return;
|
|
}
|
|
|
|
|
|
// The position of each run will be relative to the text bounds. We want to make it relative to the container bounds.
|
|
drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTL);
|
|
|
|
// We draw a rectangle above and below the text rectangle. The main text rectangle will be drawn by iterating over each visible run.
|
|
drgui_rect rectTop = drgui_make_rect(0, 0, pTL->containerWidth, textRect.top);
|
|
drgui_rect rectBottom = drgui_make_rect(0, textRect.bottom, pTL->containerWidth, pTL->containerHeight);
|
|
|
|
if (rectTop.bottom > rect.top) {
|
|
pTL->onPaintRect(pTL, rectTop, pTL->defaultBackgroundColor, pElement, pPaintData);
|
|
}
|
|
|
|
if (rectBottom.top < rect.bottom) {
|
|
pTL->onPaintRect(pTL, rectBottom, pTL->defaultBackgroundColor, pElement, pPaintData);
|
|
}
|
|
|
|
|
|
// We draw line-by-line, starting from the first visible line.
|
|
drgui_text_engine_line line;
|
|
if (drgui_text_engine__first_line(pTL, &line))
|
|
{
|
|
do
|
|
{
|
|
float lineTop = line.posY + textRect.top;
|
|
float lineBottom = lineTop + line.height;
|
|
|
|
if (lineTop < rect.bottom)
|
|
{
|
|
if (lineBottom > rect.top)
|
|
{
|
|
// The line is visible. We draw in 3 main parts - 1) the blank space to the left of the first run; 2) the runs themselves; 3) the blank
|
|
// space to the right of the last run.
|
|
|
|
drgui_color bgcolor = pTL->defaultBackgroundColor;
|
|
if (line.index == drgui_text_engine_get_cursor_line(pTL)) {
|
|
bgcolor = pTL->lineBackgroundColor;
|
|
}
|
|
|
|
float lineSelectionOverhangLeft = 0;
|
|
float lineSelectionOverhangRight = 0;
|
|
|
|
if (drgui_text_engine_is_anything_selected(pTL))
|
|
{
|
|
drgui_text_marker* pSelectionMarker0 = &pTL->selectionAnchor;
|
|
drgui_text_marker* pSelectionMarker1 = &pTL->cursor;
|
|
if (pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar > pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar)
|
|
{
|
|
drgui_text_marker* temp = pSelectionMarker0;
|
|
pSelectionMarker0 = pSelectionMarker1;
|
|
pSelectionMarker1 = temp;
|
|
}
|
|
|
|
size_t iSelectionLine0 = pTL->pRuns[pSelectionMarker0->iRun].iLine;
|
|
size_t iSelectionLine1 = pTL->pRuns[pSelectionMarker1->iRun].iLine;
|
|
|
|
if (line.index >= iSelectionLine0 && line.index < iSelectionLine1)
|
|
{
|
|
drgui_font_metrics defaultFontMetrics;
|
|
drgui_get_font_metrics(pTL->pDefaultFont, &defaultFontMetrics);
|
|
|
|
if (pTL->horzAlign == drgui_text_engine_alignment_right)
|
|
{
|
|
if (line.index > iSelectionLine0) {
|
|
lineSelectionOverhangLeft = (float)defaultFontMetrics.spaceWidth;
|
|
}
|
|
}
|
|
else if (pTL->horzAlign == drgui_text_engine_alignment_center)
|
|
{
|
|
lineSelectionOverhangRight = (float)defaultFontMetrics.spaceWidth;
|
|
|
|
if (line.index > iSelectionLine0) {
|
|
lineSelectionOverhangLeft = (float)defaultFontMetrics.spaceWidth;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lineSelectionOverhangRight = (float)defaultFontMetrics.spaceWidth;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
drgui_text_run* pFirstRun = pTL->pRuns + line.iFirstRun;
|
|
drgui_text_run* pLastRun = pTL->pRuns + line.iLastRun;
|
|
|
|
float lineLeft = pFirstRun->posX + textRect.left;
|
|
float lineRight = pLastRun->posX + pLastRun->width + textRect.left;
|
|
|
|
// 1) The blank space to the left of the first run.
|
|
if (lineLeft > 0)
|
|
{
|
|
if (lineSelectionOverhangLeft > 0) {
|
|
pTL->onPaintRect(pTL, drgui_make_rect(lineLeft - lineSelectionOverhangLeft, lineTop, lineLeft, lineBottom), pTL->selectionBackgroundColor, pElement, pPaintData);
|
|
}
|
|
|
|
pTL->onPaintRect(pTL, drgui_make_rect(0, lineTop, lineLeft - lineSelectionOverhangLeft, lineBottom), bgcolor, pElement, pPaintData);
|
|
}
|
|
|
|
|
|
// 2) The runs themselves.
|
|
for (size_t iRun = line.iFirstRun; iRun <= line.iLastRun; ++iRun)
|
|
{
|
|
drgui_text_run* pRun = pTL->pRuns + iRun;
|
|
|
|
float runLeft = pRun->posX + textRect.left;
|
|
float runRight = runLeft + pRun->width;
|
|
|
|
if (runRight > 0 && runLeft < pTL->containerWidth)
|
|
{
|
|
// The run is visible.
|
|
if (!drgui_text_engine__is_text_run_whitespace(pTL, pRun) || pTL->text[pRun->iChar] == '\t')
|
|
{
|
|
drgui_text_run run = pTL->pRuns[iRun];
|
|
run.pFont = pTL->pDefaultFont;
|
|
run.textColor = pTL->defaultTextColor;
|
|
run.backgroundColor = bgcolor;
|
|
run.text = pTL->text + run.iChar;
|
|
run.posX = runLeft;
|
|
run.posY = lineTop;
|
|
|
|
// We paint the run differently depending on whether or not anything is selected. If something is selected
|
|
// we need to split the run into a maximum of 3 sub-runs so that the selection rectangle can be drawn correctly.
|
|
if (drgui_text_engine_is_anything_selected(pTL))
|
|
{
|
|
drgui_text_run subruns[3];
|
|
size_t subrunCount = drgui_text_engine__split_text_run_by_selection(pTL, &run, subruns);
|
|
for (size_t iSubRun = 0; iSubRun < subrunCount; ++iSubRun)
|
|
{
|
|
drgui_text_run* pSubRun = subruns + iSubRun;
|
|
|
|
if (!drgui_text_engine__is_text_run_whitespace(pTL, pRun)) {
|
|
pTL->onPaintText(pTL, pSubRun, pElement, pPaintData);
|
|
} else {
|
|
pTL->onPaintRect(pTL, drgui_make_rect(pSubRun->posX, lineTop, pSubRun->posX + pSubRun->width, lineBottom), pSubRun->backgroundColor, pElement, pPaintData);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Nothing is selected.
|
|
if (!drgui_text_engine__is_text_run_whitespace(pTL, &run)) {
|
|
pTL->onPaintText(pTL, &run, pElement, pPaintData);
|
|
} else {
|
|
pTL->onPaintRect(pTL, drgui_make_rect(run.posX, lineTop, run.posX + run.width, lineBottom), run.backgroundColor, pElement, pPaintData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 3) The blank space to the right of the last run.
|
|
if (lineRight < pTL->containerWidth)
|
|
{
|
|
if (lineSelectionOverhangRight > 0) {
|
|
pTL->onPaintRect(pTL, drgui_make_rect(lineRight, lineTop, lineRight + lineSelectionOverhangRight, lineBottom), pTL->selectionBackgroundColor, pElement, pPaintData);
|
|
}
|
|
|
|
pTL->onPaintRect(pTL, drgui_make_rect(lineRight + lineSelectionOverhangRight, lineTop, pTL->containerWidth, lineBottom), bgcolor, pElement, pPaintData);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The line is below the rectangle which means no other line will be visible and we can terminate early.
|
|
break;
|
|
}
|
|
|
|
} while (drgui_text_engine__next_line(pTL, &line));
|
|
}
|
|
else
|
|
{
|
|
// There are no lines so we do a simplified branch here.
|
|
float lineTop = textRect.top;
|
|
float lineBottom = textRect.bottom;
|
|
pTL->onPaintRect(pTL, drgui_make_rect(0, lineTop, pTL->containerWidth, lineBottom), pTL->lineBackgroundColor, pElement, pPaintData);
|
|
}
|
|
|
|
// The cursor.
|
|
if (pTL->isShowingCursor && pTL->isCursorBlinkOn) {
|
|
pTL->onPaintRect(pTL, drgui_text_engine_get_cursor_rect(pTL), pTL->cursorColor, pElement, pPaintData);
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_text_engine_step(drgui_text_engine* pTL, unsigned int milliseconds)
|
|
{
|
|
if (pTL == NULL || milliseconds == 0) {
|
|
return;
|
|
}
|
|
|
|
if (pTL->timeToNextCursorBlink < milliseconds)
|
|
{
|
|
pTL->isCursorBlinkOn = !pTL->isCursorBlinkOn;
|
|
pTL->timeToNextCursorBlink = pTL->cursorBlinkRate;
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine_get_cursor_rect(pTL));
|
|
}
|
|
else
|
|
{
|
|
pTL->timeToNextCursorBlink -= milliseconds;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void drgui_text_engine_paint_line_numbers(drgui_text_engine* pTL, float lineNumbersWidth, float lineNumbersHeight, drgui_color textColor, drgui_color backgroundColor, drgui_text_engine_on_paint_text_proc onPaintText, drgui_text_engine_on_paint_rect_proc onPaintRect, drgui_element* pElement, void* pPaintData)
|
|
{
|
|
if (pTL == NULL || onPaintText == NULL || onPaintRect == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
// The position of each run will be relative to the text bounds. We want to make it relative to the container bounds.
|
|
drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTL);
|
|
|
|
// We draw a rectangle above and below the text rectangle. The main text rectangle will be drawn by iterating over each visible run.
|
|
drgui_rect rectTop = drgui_make_rect(0, 0, lineNumbersWidth, textRect.top);
|
|
drgui_rect rectBottom = drgui_make_rect(0, textRect.bottom, lineNumbersWidth, lineNumbersHeight);
|
|
|
|
if (pTL->onPaintRect)
|
|
{
|
|
if (rectTop.bottom > 0) {
|
|
onPaintRect(pTL, rectTop, backgroundColor, pElement, pPaintData);
|
|
}
|
|
|
|
if (rectBottom.top < lineNumbersHeight) {
|
|
onPaintRect(pTL, rectBottom, backgroundColor, pElement, pPaintData);
|
|
}
|
|
}
|
|
|
|
|
|
// Now we draw each line.
|
|
int iLine = 1;
|
|
drgui_text_engine_line line;
|
|
if (!drgui_text_engine__first_line(pTL, &line))
|
|
{
|
|
// We failed to retrieve the first line which is probably due to the text engine being empty. We just fake the first line to
|
|
// ensure we get the number 1 to be drawn.
|
|
drgui_font_metrics fontMetrics;
|
|
drgui_get_font_metrics(pTL->pDefaultFont, &fontMetrics);
|
|
|
|
line.height = (float)fontMetrics.lineHeight;
|
|
line.posY = 0;
|
|
}
|
|
|
|
do
|
|
{
|
|
float lineTop = line.posY + textRect.top;
|
|
float lineBottom = lineTop + line.height;
|
|
|
|
if (lineTop < lineNumbersHeight)
|
|
{
|
|
if (lineBottom > 0)
|
|
{
|
|
char iLineStr[64];
|
|
#ifdef _MSC_VER
|
|
_itoa_s(iLine, iLineStr, sizeof(iLineStr), 10);
|
|
#else
|
|
snprintf(iLineStr, sizeof(iLineStr), "%d", iLine);
|
|
#endif
|
|
|
|
drgui_font* pFont = pTL->pDefaultFont;
|
|
|
|
float textWidth;
|
|
float textHeight;
|
|
drgui_measure_string(pFont, iLineStr, strlen(iLineStr), &textWidth, &textHeight);
|
|
|
|
drgui_text_run run = {0};
|
|
run.pFont = pFont;
|
|
run.textColor = textColor;
|
|
run.backgroundColor = backgroundColor;
|
|
run.text = iLineStr;
|
|
run.textLength = strlen(iLineStr);
|
|
run.posX = lineNumbersWidth - textWidth;
|
|
run.posY = lineTop;
|
|
onPaintText(pTL, &run, pElement, pPaintData);
|
|
onPaintRect(pTL, drgui_make_rect(0, lineTop, run.posX, lineBottom), run.backgroundColor, pElement, pPaintData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The line is below the rectangle which means no other line will be visible and we can terminate early.
|
|
break;
|
|
}
|
|
|
|
iLine += 1;
|
|
} while (drgui_text_engine__next_line(pTL, &line));
|
|
}
|
|
|
|
|
|
bool drgui_text_engine_find_next(drgui_text_engine* pTL, const char* text, size_t* pSelectionStartOut, size_t* pSelectionEndOut)
|
|
{
|
|
if (pTL == NULL || pTL->text == NULL || text == NULL || text[0] == '\0') {
|
|
return false;
|
|
}
|
|
|
|
size_t cursorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor);
|
|
char* nextOccurance = strstr(pTL->text + cursorPos, text);
|
|
if (nextOccurance == NULL) {
|
|
nextOccurance = strstr(pTL->text, text);
|
|
}
|
|
|
|
if (nextOccurance == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (pSelectionStartOut) {
|
|
*pSelectionStartOut = nextOccurance - pTL->text;
|
|
}
|
|
if (pSelectionEndOut) {
|
|
*pSelectionEndOut = (nextOccurance - pTL->text) + strlen(text);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool drgui_text_engine_find_next_no_loop(drgui_text_engine* pTL, const char* text, size_t* pSelectionStartOut, size_t* pSelectionEndOut)
|
|
{
|
|
if (pTL == NULL || pTL->text == NULL || text == NULL || text[0] == '\0') {
|
|
return false;
|
|
}
|
|
|
|
size_t cursorPos = drgui_text_engine__get_marker_absolute_char_index(pTL, &pTL->cursor);
|
|
|
|
char* nextOccurance = strstr(pTL->text + cursorPos, text);
|
|
if (nextOccurance == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (pSelectionStartOut) {
|
|
*pSelectionStartOut = nextOccurance - pTL->text;
|
|
}
|
|
if (pSelectionEndOut) {
|
|
*pSelectionEndOut = (nextOccurance - pTL->text) + strlen(text);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
DRGUI_PRIVATE bool drgui_next_run_string(const char* runStart, const char* textEndPastNullTerminator, const char** pRunEndOut)
|
|
{
|
|
assert(runStart <= textEndPastNullTerminator);
|
|
|
|
if (runStart == NULL || runStart == textEndPastNullTerminator)
|
|
{
|
|
// String is empty.
|
|
return false;
|
|
}
|
|
|
|
|
|
char firstChar = runStart[0];
|
|
if (firstChar == '\t')
|
|
{
|
|
// We loop until we hit anything that is not a tab character (tabs will be grouped together into a single run).
|
|
do
|
|
{
|
|
runStart += 1;
|
|
*pRunEndOut = runStart;
|
|
} while (runStart[0] != '\0' && runStart[0] == '\t');
|
|
}
|
|
else if (firstChar == '\n')
|
|
{
|
|
runStart += 1;
|
|
*pRunEndOut = runStart;
|
|
}
|
|
else if (firstChar == '\0')
|
|
{
|
|
assert(runStart + 1 == textEndPastNullTerminator);
|
|
*pRunEndOut = textEndPastNullTerminator;
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
runStart += 1;
|
|
*pRunEndOut = runStart;
|
|
} while (runStart[0] != '\0' && runStart[0] != '\t' && runStart[0] != '\n');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__refresh(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
// We split the runs based on tabs and new-lines. We want to create runs for tabs and new-line characters as well because we want
|
|
// to have the entire string covered by runs for the sake of simplicity when it comes to editing.
|
|
//
|
|
// The first pass positions the runs based on a top-to-bottom, left-to-right alignment. The second pass then repositions the runs
|
|
// based on alignment.
|
|
|
|
// Runs need to be cleared first.
|
|
drgui_text_engine__clear_text_runs(pTL);
|
|
|
|
// The text bounds also need to be reset at the top.
|
|
pTL->textBoundsWidth = 0;
|
|
pTL->textBoundsHeight = 0;
|
|
|
|
drgui_font_metrics defaultFontMetrics;
|
|
drgui_get_font_metrics(pTL->pDefaultFont, &defaultFontMetrics);
|
|
|
|
pTL->textBoundsHeight = (float)defaultFontMetrics.lineHeight;
|
|
|
|
const float tabWidth = drgui_text_engine__get_tab_width(pTL);
|
|
|
|
size_t iCurrentLine = 0;
|
|
float runningPosY = 0;
|
|
float runningLineHeight = 0;
|
|
|
|
const char* nextRunStart = pTL->text;
|
|
const char* nextRunEnd;
|
|
while (drgui_next_run_string(nextRunStart, pTL->text + pTL->textLength + 1, OUT &nextRunEnd))
|
|
{
|
|
drgui_text_run run;
|
|
run.iLine = iCurrentLine;
|
|
run.iChar = nextRunStart - pTL->text;
|
|
run.iCharEnd = nextRunEnd - pTL->text;
|
|
run.textLength = nextRunEnd - nextRunStart;
|
|
run.width = 0;
|
|
run.height = 0;
|
|
run.posX = 0;
|
|
run.posY = runningPosY;
|
|
run.pFont = pTL->pDefaultFont;
|
|
|
|
// X position
|
|
//
|
|
// The x position depends on the previous run that's on the same line.
|
|
if (pTL->runCount > 0)
|
|
{
|
|
drgui_text_run* pPrevRun = pTL->pRuns + (pTL->runCount - 1);
|
|
if (pPrevRun->iLine == iCurrentLine)
|
|
{
|
|
run.posX = pPrevRun->posX + pPrevRun->width;
|
|
}
|
|
else
|
|
{
|
|
// It's the first run on the line.
|
|
run.posX = 0;
|
|
}
|
|
}
|
|
|
|
|
|
// Width and height.
|
|
assert(nextRunEnd > nextRunStart);
|
|
if (nextRunStart[0] == '\t')
|
|
{
|
|
// Tab.
|
|
size_t tabCount = run.iCharEnd - run.iChar;
|
|
run.width = (float)(((tabCount*(size_t)tabWidth) - ((size_t)run.posX % (size_t)tabWidth)));
|
|
run.height = (float)defaultFontMetrics.lineHeight;
|
|
}
|
|
else if (nextRunStart[0] == '\n')
|
|
{
|
|
// New line.
|
|
iCurrentLine += 1;
|
|
run.width = 0;
|
|
run.height = (float)defaultFontMetrics.lineHeight;
|
|
}
|
|
else if (nextRunStart[0] == '\0')
|
|
{
|
|
// Null terminator.
|
|
run.width = 0;
|
|
run.height = (float)defaultFontMetrics.lineHeight;
|
|
run.textLength = 0;
|
|
}
|
|
else
|
|
{
|
|
// Normal run.
|
|
drgui_measure_string(pTL->pDefaultFont, nextRunStart, run.textLength, &run.width, &run.height);
|
|
}
|
|
|
|
|
|
// The running line height needs to be updated by setting to the maximum size.
|
|
runningLineHeight = (run.height > runningLineHeight) ? run.height : runningLineHeight;
|
|
|
|
|
|
// Update the text bounds.
|
|
if (pTL->textBoundsWidth < run.posX + run.width) {
|
|
pTL->textBoundsWidth = run.posX + run.width;
|
|
}
|
|
pTL->textBoundsHeight = runningPosY + runningLineHeight;
|
|
|
|
|
|
// A new line means we need to increment the running y position by the running line height.
|
|
if (nextRunStart[0] == '\n')
|
|
{
|
|
runningPosY += runningLineHeight;
|
|
runningLineHeight = 0;
|
|
}
|
|
|
|
// Add the run to the internal list.
|
|
drgui_text_engine__push_text_run(pTL, &run);
|
|
|
|
// Go to the next run string.
|
|
nextRunStart = nextRunEnd;
|
|
}
|
|
|
|
// If we were to return now the text would be alignment top/left. If the alignment is not top/left we need to refresh the layout.
|
|
if (pTL->horzAlign != drgui_text_engine_alignment_left || pTL->vertAlign != drgui_text_engine_alignment_top)
|
|
{
|
|
drgui_text_engine__refresh_alignment(pTL);
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__refresh_alignment(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
float runningPosY = 0;
|
|
|
|
unsigned int iCurrentLine = 0;
|
|
for (size_t iRun = 0; iRun < pTL->runCount; /* Do Nothing*/) // iRun is incremented from within the loop.
|
|
{
|
|
float lineWidth = 0;
|
|
float lineHeight = 0;
|
|
|
|
// This loop does a few things. First, it defines the end point for the loop after this one (jRun). Second, it calculates
|
|
// the line width which is needed for center and right alignment. Third it resets the position of each run to their
|
|
// unaligned equivalents which will be offsetted in the second loop.
|
|
size_t jRun;
|
|
for (jRun = iRun; jRun < pTL->runCount && pTL->pRuns[jRun].iLine == iCurrentLine; ++jRun)
|
|
{
|
|
drgui_text_run* pRun = pTL->pRuns + jRun;
|
|
pRun->posX = lineWidth;
|
|
pRun->posY = runningPosY;
|
|
|
|
lineWidth += pRun->width;
|
|
lineHeight = (lineHeight > pRun->height) ? lineHeight : pRun->height;
|
|
}
|
|
|
|
|
|
// The actual alignment is done here.
|
|
float lineOffsetX;
|
|
float lineOffsetY;
|
|
drgui_text_engine__calculate_line_alignment_offset(pTL, lineWidth, OUT &lineOffsetX, OUT &lineOffsetY);
|
|
|
|
for (/* Do Nothing*/; iRun < jRun; ++iRun)
|
|
{
|
|
drgui_text_run* pRun = pTL->pRuns + iRun;
|
|
pRun->posX += lineOffsetX;
|
|
pRun->posY += lineOffsetY;
|
|
}
|
|
|
|
|
|
// Go to the next line.
|
|
iCurrentLine += 1;
|
|
runningPosY += lineHeight;
|
|
}
|
|
}
|
|
|
|
void drgui_text_engine__calculate_line_alignment_offset(drgui_text_engine* pTL, float lineWidth, float* pOffsetXOut, float* pOffsetYOut)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
float offsetX = 0;
|
|
float offsetY = 0;
|
|
|
|
switch (pTL->horzAlign)
|
|
{
|
|
case drgui_text_engine_alignment_right:
|
|
{
|
|
offsetX = pTL->textBoundsWidth - lineWidth;
|
|
break;
|
|
}
|
|
|
|
case drgui_text_engine_alignment_center:
|
|
{
|
|
offsetX = (pTL->textBoundsWidth - lineWidth) / 2;
|
|
break;
|
|
}
|
|
|
|
case drgui_text_engine_alignment_left:
|
|
case drgui_text_engine_alignment_top: // Invalid for horizontal alignment.
|
|
case drgui_text_engine_alignment_bottom: // Invalid for horizontal alignment.
|
|
default:
|
|
{
|
|
offsetX = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
switch (pTL->vertAlign)
|
|
{
|
|
case drgui_text_engine_alignment_bottom:
|
|
{
|
|
offsetY = pTL->textBoundsHeight - pTL->textBoundsHeight;
|
|
break;
|
|
}
|
|
|
|
case drgui_text_engine_alignment_center:
|
|
{
|
|
offsetY = (pTL->textBoundsHeight - pTL->textBoundsHeight) / 2;
|
|
break;
|
|
}
|
|
|
|
case drgui_text_engine_alignment_top:
|
|
case drgui_text_engine_alignment_left: // Invalid for vertical alignment.
|
|
case drgui_text_engine_alignment_right: // Invalid for vertical alignment.
|
|
default:
|
|
{
|
|
offsetY = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (pOffsetXOut) {
|
|
*pOffsetXOut = offsetX;
|
|
}
|
|
if (pOffsetYOut) {
|
|
*pOffsetYOut = offsetY;
|
|
}
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__push_text_run(drgui_text_engine* pTL, drgui_text_run* pRun)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTL->runBufferSize == pTL->runCount)
|
|
{
|
|
pTL->runBufferSize = pTL->runBufferSize*2;
|
|
if (pTL->runBufferSize == 0) {
|
|
pTL->runBufferSize = 1;
|
|
}
|
|
|
|
pTL->pRuns = (drgui_text_run*)realloc(pTL->pRuns, sizeof(drgui_text_run) * pTL->runBufferSize);
|
|
if (pTL->pRuns == NULL) {
|
|
pTL->runCount = 0;
|
|
pTL->runBufferSize = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
pTL->pRuns[pTL->runCount] = *pRun;
|
|
pTL->runCount += 1;
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__clear_text_runs(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->runCount = 0;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__is_text_run_whitespace(drgui_text_engine* pTL, drgui_text_run* pRun)
|
|
{
|
|
if (pRun == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (pTL->text[pRun->iChar] != '\t' && pTL->text[pRun->iChar] != '\n')
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DRGUI_PRIVATE float drgui_text_engine__get_tab_width(drgui_text_engine* pTL)
|
|
{
|
|
drgui_font_metrics defaultFontMetrics;
|
|
drgui_get_font_metrics(pTL->pDefaultFont, &defaultFontMetrics);
|
|
|
|
return (float)(defaultFontMetrics.spaceWidth * pTL->tabSizeInSpaces);
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_closest_line_to_point(drgui_text_engine* pTL, float inputPosYRelativeToText, size_t* pFirstRunIndexOnLineOut, size_t* pLastRunIndexOnLinePlus1Out)
|
|
{
|
|
size_t iFirstRunOnLine = 0;
|
|
size_t iLastRunOnLinePlus1 = 0;
|
|
|
|
bool result = true;
|
|
if (pTL == NULL || pTL->runCount == 0)
|
|
{
|
|
result = false;
|
|
}
|
|
else
|
|
{
|
|
float runningLineTop = 0;
|
|
|
|
float lineHeight;
|
|
while (drgui_text_engine__find_line_info(pTL, iFirstRunOnLine, OUT &iLastRunOnLinePlus1, OUT &lineHeight))
|
|
{
|
|
const float lineTop = runningLineTop;
|
|
const float lineBottom = lineTop + lineHeight;
|
|
|
|
if (inputPosYRelativeToText < lineBottom)
|
|
{
|
|
// It's on this line.
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// It's not on this line - go to the next one, unless we're already on the last line.
|
|
if (iLastRunOnLinePlus1 == pTL->runCount) {
|
|
break;
|
|
}
|
|
|
|
iFirstRunOnLine = iLastRunOnLinePlus1;
|
|
runningLineTop = lineBottom;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pFirstRunIndexOnLineOut) {
|
|
*pFirstRunIndexOnLineOut = iFirstRunOnLine;
|
|
}
|
|
if (pLastRunIndexOnLinePlus1Out) {
|
|
*pLastRunIndexOnLinePlus1Out = iLastRunOnLinePlus1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_closest_run_to_point(drgui_text_engine* pTL, float inputPosXRelativeToText, float inputPosYRelativeToText, size_t* pRunIndexOut)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iFirstRunOnLine;
|
|
size_t iLastRunOnLinePlus1;
|
|
if (drgui_text_engine__find_closest_line_to_point(pTL, inputPosYRelativeToText, OUT &iFirstRunOnLine, OUT &iLastRunOnLinePlus1))
|
|
{
|
|
size_t iRunOut = 0;
|
|
|
|
const drgui_text_run* pFirstRunOnLine = pTL->pRuns + iFirstRunOnLine;
|
|
const drgui_text_run* pLastRunOnLine = pTL->pRuns + (iLastRunOnLinePlus1 - 1);
|
|
|
|
if (inputPosXRelativeToText < pFirstRunOnLine->posX)
|
|
{
|
|
// It's to the left of the first run.
|
|
iRunOut = iFirstRunOnLine;
|
|
}
|
|
else if (inputPosXRelativeToText > pLastRunOnLine->posX + pLastRunOnLine->width)
|
|
{
|
|
// It's to the right of the last run.
|
|
iRunOut = iLastRunOnLinePlus1 - 1;
|
|
}
|
|
else
|
|
{
|
|
// It's in the middle of the line. We just iterate over each run on the line and return the first one that contains the point.
|
|
for (size_t iRun = iFirstRunOnLine; iRun < iLastRunOnLinePlus1; ++iRun)
|
|
{
|
|
const drgui_text_run* pRun = pTL->pRuns + iRun;
|
|
iRunOut = iRun;
|
|
|
|
if (inputPosXRelativeToText >= pRun->posX && inputPosXRelativeToText <= pRun->posX + pRun->width) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pRunIndexOut) {
|
|
*pRunIndexOut = iRunOut;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// There was an error finding the closest line.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_line_info(drgui_text_engine* pTL, size_t iFirstRunOnLine, size_t* pLastRunIndexOnLinePlus1Out, float* pLineHeightOut)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (iFirstRunOnLine < pTL->runCount)
|
|
{
|
|
const size_t iLine = pTL->pRuns[iFirstRunOnLine].iLine;
|
|
float lineHeight = 0;
|
|
|
|
size_t iRun;
|
|
for (iRun = iFirstRunOnLine; iRun < pTL->runCount && pTL->pRuns[iRun].iLine == iLine; ++iRun)
|
|
{
|
|
if (lineHeight < pTL->pRuns[iRun].height) {
|
|
lineHeight = pTL->pRuns[iRun].height;
|
|
}
|
|
}
|
|
|
|
assert(iRun > iFirstRunOnLine);
|
|
|
|
if (pLastRunIndexOnLinePlus1Out) {
|
|
*pLastRunIndexOnLinePlus1Out = iRun;
|
|
}
|
|
if (pLineHeightOut) {
|
|
*pLineHeightOut = lineHeight;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_line_info_by_index(drgui_text_engine* pTL, size_t iLine, drgui_rect* pRectOut, size_t* pFirstRunIndexOut, size_t* pLastRunIndexPlus1Out)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iFirstRunOnLine = 0;
|
|
size_t iLastRunOnLinePlus1 = 0;
|
|
|
|
float lineTop = 0;
|
|
float lineHeight = 0;
|
|
|
|
for (size_t iCurrentLine = 0; iCurrentLine <= iLine; ++iCurrentLine)
|
|
{
|
|
iFirstRunOnLine = iLastRunOnLinePlus1;
|
|
lineTop += lineHeight;
|
|
|
|
if (!drgui_text_engine__find_line_info(pTL, iFirstRunOnLine, &iLastRunOnLinePlus1, &lineHeight))
|
|
{
|
|
// There was an error retrieving information about the line.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
// At this point we have the first and last runs that make up the line and we can generate our output.
|
|
if (iLastRunOnLinePlus1 > iFirstRunOnLine)
|
|
{
|
|
if (pFirstRunIndexOut) {
|
|
*pFirstRunIndexOut = iFirstRunOnLine;
|
|
}
|
|
if (pLastRunIndexPlus1Out) {
|
|
*pLastRunIndexPlus1Out = iLastRunOnLinePlus1;
|
|
}
|
|
|
|
if (pRectOut != NULL)
|
|
{
|
|
pRectOut->left = pTL->pRuns[iFirstRunOnLine].posX;
|
|
pRectOut->right = pTL->pRuns[iLastRunOnLinePlus1 - 1].posX + pTL->pRuns[iLastRunOnLinePlus1 - 1].width;
|
|
pRectOut->top = lineTop;
|
|
pRectOut->bottom = pRectOut->top + lineHeight;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// We couldn't find any runs.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_last_run_on_line_starting_from_run(drgui_text_engine* pTL, size_t iRun, size_t* pLastRunIndexOnLineOut)
|
|
{
|
|
if (pTL == NULL || pTL->pRuns == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t result = iRun;
|
|
|
|
size_t iLine = pTL->pRuns[iRun].iLine;
|
|
for (/* Do Nothing*/; iRun < pTL->runCount && pTL->pRuns[iRun].iLine == iLine; ++iRun)
|
|
{
|
|
result = iRun;
|
|
}
|
|
|
|
if (pLastRunIndexOnLineOut) {
|
|
*pLastRunIndexOnLineOut = result;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_first_run_on_line_starting_from_run(drgui_text_engine* pTL, size_t iRun, size_t* pFirstRunIndexOnLineOut)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t result = iRun;
|
|
|
|
size_t iLine = pTL->pRuns[iRun].iLine;
|
|
for (/* Do Nothing*/; iRun > 0 && pTL->pRuns[iRun - 1].iLine == iLine; --iRun)
|
|
{
|
|
result = iRun - 1;
|
|
}
|
|
|
|
if (pFirstRunIndexOnLineOut) {
|
|
*pFirstRunIndexOnLineOut = result;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__find_run_at_character(drgui_text_engine* pTL, size_t iChar, size_t* pRunIndexOut)
|
|
{
|
|
if (pTL == NULL || pTL->runCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
size_t result = 0;
|
|
if (iChar < pTL->textLength)
|
|
{
|
|
for (size_t iRun = 0; iRun < pTL->runCount; ++iRun)
|
|
{
|
|
const drgui_text_run* pRun = pTL->pRuns + iRun;
|
|
|
|
if (iChar < pRun->iCharEnd)
|
|
{
|
|
result = iRun;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The character index is too high. Return the last run.
|
|
result = pTL->runCount - 1;
|
|
}
|
|
|
|
if (pRunIndexOut) {
|
|
*pRunIndexOut = result;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE drgui_text_marker drgui_text_engine__new_marker()
|
|
{
|
|
drgui_text_marker marker;
|
|
marker.iRun = 0;
|
|
marker.iChar = 0;
|
|
marker.relativePosX = 0;
|
|
marker.absoluteSickyPosX = 0;
|
|
|
|
return marker;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_point_relative_to_container(drgui_text_engine* pTL, drgui_text_marker* pMarker, float inputPosX, float inputPosY)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
pMarker->iRun = 0;
|
|
pMarker->iChar = 0;
|
|
pMarker->relativePosX = 0;
|
|
pMarker->absoluteSickyPosX = 0;
|
|
|
|
drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTL);
|
|
|
|
float inputPosXRelativeToText = inputPosX - textRect.left;
|
|
float inputPosYRelativeToText = inputPosY - textRect.top;
|
|
if (drgui_text_engine__move_marker_to_point(pTL, pMarker, inputPosXRelativeToText, inputPosYRelativeToText))
|
|
{
|
|
drgui_text_engine__update_marker_sticky_position(pTL, pMarker);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__get_marker_position_relative_to_container(drgui_text_engine* pTL, drgui_text_marker* pMarker, float* pPosXOut, float* pPosYOut)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return;
|
|
}
|
|
|
|
float posX = 0;
|
|
float posY = 0;
|
|
|
|
if (pMarker->iRun < pTL->runCount)
|
|
{
|
|
posX = pTL->pRuns[pMarker->iRun].posX + pMarker->relativePosX;
|
|
posY = pTL->pRuns[pMarker->iRun].posY;
|
|
}
|
|
|
|
drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTL);
|
|
posX += textRect.left;
|
|
posY += textRect.top;
|
|
|
|
|
|
if (pPosXOut) {
|
|
*pPosXOut = posX;
|
|
}
|
|
if (pPosYOut) {
|
|
*pPosYOut = posY;
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_point(drgui_text_engine* pTL, drgui_text_marker* pMarker, float inputPosXRelativeToText, float inputPosYRelativeToText)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iClosestRunToPoint;
|
|
if (drgui_text_engine__find_closest_run_to_point(pTL, inputPosXRelativeToText, inputPosYRelativeToText, OUT &iClosestRunToPoint))
|
|
{
|
|
const drgui_text_run* pRun = pTL->pRuns + iClosestRunToPoint;
|
|
|
|
pMarker->iRun = iClosestRunToPoint;
|
|
|
|
if (inputPosXRelativeToText < pRun->posX)
|
|
{
|
|
// It's to the left of the run.
|
|
pMarker->iChar = 0;
|
|
pMarker->relativePosX = 0;
|
|
}
|
|
else if (inputPosXRelativeToText > pRun->posX + pRun->width)
|
|
{
|
|
// It's to the right of the run. It may be a new-line run. If so, we need to move the marker to the front of it, not the back.
|
|
pMarker->iChar = pRun->textLength;
|
|
pMarker->relativePosX = pRun->width;
|
|
|
|
if (pTL->text[pRun->iChar] == '\n') {
|
|
assert(pMarker->iChar == 1);
|
|
pMarker->iChar = 0;
|
|
pMarker->relativePosX = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// It's somewhere in the middle of the run. We need to handle this a little different for tab runs since they are aligned differently.
|
|
if (pTL->text[pRun->iChar] == '\n')
|
|
{
|
|
// It's a new line character. It needs to be placed at the beginning of it.
|
|
pMarker->iChar = 0;
|
|
pMarker->relativePosX = 0;
|
|
}
|
|
else if (pTL->text[pRun->iChar] == '\t')
|
|
{
|
|
// It's a tab run.
|
|
pMarker->iChar = 0;
|
|
pMarker->relativePosX = 0;
|
|
|
|
const float tabWidth = drgui_text_engine__get_tab_width(pTL);
|
|
|
|
float tabLeft = pRun->posX + pMarker->relativePosX;
|
|
for (/* Do Nothing*/; pMarker->iChar < pRun->textLength; ++pMarker->iChar)
|
|
{
|
|
float tabRight = tabWidth * ((pRun->posX + (tabWidth*(pMarker->iChar + 1))) / tabWidth);
|
|
if (tabRight > pRun->posX + pRun->width) {
|
|
tabRight = pRun->posX + pRun->width;
|
|
}
|
|
|
|
if (inputPosXRelativeToText >= tabLeft && inputPosXRelativeToText <= tabRight)
|
|
{
|
|
// The input position is somewhere on top of this character. If it's positioned on the left side of the character, set the output
|
|
// value to the character at iChar. Otherwise it should be set to the character at iChar + 1.
|
|
float charBoundsRightHalf = tabLeft + ceilf(((tabRight - tabLeft) / 2.0f));
|
|
if (inputPosXRelativeToText <= charBoundsRightHalf) {
|
|
pMarker->relativePosX = tabLeft - pRun->posX;
|
|
} else {
|
|
pMarker->relativePosX = tabRight - pRun->posX;
|
|
pMarker->iChar += 1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
tabLeft = tabRight;
|
|
}
|
|
|
|
// If we're past the last character in the tab run, we want to move to the start of the next run.
|
|
if (pMarker->iChar == pRun->textLength) {
|
|
drgui_text_engine__move_marker_to_first_character_of_next_run(pTL, pMarker);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// It's a standard run.
|
|
float inputPosXRelativeToRun = inputPosXRelativeToText - pRun->posX;
|
|
if (drgui_get_text_cursor_position_from_point(pRun->pFont, pTL->text + pRun->iChar, pRun->textLength, pRun->width, inputPosXRelativeToRun, OUT &pMarker->relativePosX, OUT &pMarker->iChar))
|
|
{
|
|
// If the marker is past the last character of the run it needs to be moved to the start of the next one.
|
|
if (pMarker->iChar == pRun->textLength) {
|
|
drgui_text_engine__move_marker_to_first_character_of_next_run(pTL, pMarker);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// An error occured somehow.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Couldn't find a run.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_left(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (pTL->runCount > 0)
|
|
{
|
|
if (pMarker->iChar > 0)
|
|
{
|
|
pMarker->iChar -= 1;
|
|
|
|
const drgui_text_run* pRun = pTL->pRuns + pMarker->iRun;
|
|
if (pTL->text[pRun->iChar] == '\t')
|
|
{
|
|
const float tabWidth = drgui_text_engine__get_tab_width(pTL);
|
|
|
|
if (pMarker->iChar == 0)
|
|
{
|
|
// Simple case - it's the first tab character which means the relative position is just 0.
|
|
pMarker->relativePosX = 0;
|
|
}
|
|
else
|
|
{
|
|
pMarker->relativePosX = tabWidth * ((pRun->posX + (tabWidth*(pMarker->iChar + 0))) / tabWidth);
|
|
pMarker->relativePosX -= pRun->posX;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!drgui_get_text_cursor_position_from_char(pRun->pFont, pTL->text + pTL->pRuns[pMarker->iRun].iChar, pMarker->iChar, OUT &pMarker->relativePosX)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We're at the beginning of the run which means we need to transfer the cursor to the end of the previous run.
|
|
if (!drgui_text_engine__move_marker_to_last_character_of_prev_run(pTL, pMarker)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
drgui_text_engine__update_marker_sticky_position(pTL, pMarker);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_right(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (pTL->runCount > 0)
|
|
{
|
|
if (pMarker->iChar + 1 < pTL->pRuns[pMarker->iRun].textLength)
|
|
{
|
|
pMarker->iChar += 1;
|
|
|
|
const drgui_text_run* pRun = pTL->pRuns + pMarker->iRun;
|
|
if (pTL->text[pRun->iChar] == '\t')
|
|
{
|
|
const float tabWidth = drgui_text_engine__get_tab_width(pTL);
|
|
|
|
pMarker->relativePosX = tabWidth * ((pRun->posX + (tabWidth*(pMarker->iChar + 0))) / tabWidth);
|
|
pMarker->relativePosX -= pRun->posX;
|
|
}
|
|
else
|
|
{
|
|
if (!drgui_get_text_cursor_position_from_char(pRun->pFont, pTL->text + pTL->pRuns[pMarker->iRun].iChar, pMarker->iChar, OUT &pMarker->relativePosX)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We're at the end of the run which means we need to transfer the cursor to the beginning of the next run.
|
|
if (!drgui_text_engine__move_marker_to_first_character_of_next_run(pTL, pMarker)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
drgui_text_engine__update_marker_sticky_position(pTL, pMarker);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_up(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_text_engine__move_marker_y(pTL, pMarker, -1);
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_down(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_text_engine__move_marker_y(pTL, pMarker, 1);
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_y(drgui_text_engine* pTL, drgui_text_marker* pMarker, int amount)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
const drgui_text_run* pOldRun = pTL->pRuns + pMarker->iRun;
|
|
|
|
int iNewLine = (int)pOldRun->iLine + amount;
|
|
if (iNewLine >= (int)drgui_text_engine_get_line_count(pTL)) {
|
|
iNewLine = (int)drgui_text_engine_get_line_count(pTL) - 1;
|
|
}
|
|
if (iNewLine < 0) {
|
|
iNewLine = 0;
|
|
}
|
|
|
|
if ((int)pOldRun->iLine == iNewLine) {
|
|
return false; // The lines are the same.
|
|
}
|
|
|
|
drgui_rect lineRect;
|
|
size_t iFirstRunOnLine;
|
|
size_t iLastRunOnLinePlus1;
|
|
if (drgui_text_engine__find_line_info_by_index(pTL, iNewLine, OUT &lineRect, OUT &iFirstRunOnLine, OUT &iLastRunOnLinePlus1))
|
|
{
|
|
float newMarkerPosX = pMarker->absoluteSickyPosX;
|
|
float newMarkerPosY = lineRect.top;
|
|
drgui_text_engine__move_marker_to_point(pTL, pMarker, newMarkerPosX, newMarkerPosY);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// An error occured while finding information about the line above.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_line(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iLastRunOnLine;
|
|
if (drgui_text_engine__find_last_run_on_line_starting_from_run(pTL, pMarker->iRun, &iLastRunOnLine))
|
|
{
|
|
return drgui_text_engine__move_marker_to_last_character_of_run(pTL, pMarker, iLastRunOnLine);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_line(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iFirstRunOnLine;
|
|
if (drgui_text_engine__find_first_run_on_line_starting_from_run(pTL, pMarker->iRun, &iFirstRunOnLine))
|
|
{
|
|
return drgui_text_engine__move_marker_to_first_character_of_run(pTL, pMarker, iFirstRunOnLine);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_line_by_index(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iLine)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iFirstRun;
|
|
size_t iLastRunPlus1;
|
|
if (drgui_text_engine__find_line_info_by_index(pTL, iLine, NULL, &iFirstRun, &iLastRunPlus1))
|
|
{
|
|
return drgui_text_engine__move_marker_to_last_character_of_run(pTL, pMarker, iLastRunPlus1 - 1);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_line_by_index(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iLine)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t iFirstRun;
|
|
size_t iLastRunPlus1;
|
|
if (drgui_text_engine__find_line_info_by_index(pTL, iLine, NULL, &iFirstRun, &iLastRunPlus1))
|
|
{
|
|
return drgui_text_engine__move_marker_to_first_character_of_run(pTL, pMarker, iFirstRun);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_end_of_text(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (pTL->runCount > 0) {
|
|
return drgui_text_engine__move_marker_to_last_character_of_run(pTL, pMarker, pTL->runCount - 1);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_start_of_text(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_text_engine__move_marker_to_first_character_of_run(pTL, pMarker, 0);
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_last_character_of_run(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iRun)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (iRun < pTL->runCount)
|
|
{
|
|
pMarker->iRun = iRun;
|
|
pMarker->iChar = pTL->pRuns[pMarker->iRun].textLength;
|
|
pMarker->relativePosX = pTL->pRuns[pMarker->iRun].width;
|
|
|
|
if (pMarker->iChar > 0)
|
|
{
|
|
// At this point we are located one character past the last character - we need to move it left.
|
|
return drgui_text_engine__move_marker_left(pTL, pMarker);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_first_character_of_run(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iRun)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (iRun < pTL->runCount)
|
|
{
|
|
pMarker->iRun = iRun;
|
|
pMarker->iChar = 0;
|
|
pMarker->relativePosX = 0;
|
|
|
|
drgui_text_engine__update_marker_sticky_position(pTL, pMarker);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_last_character_of_prev_run(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (pMarker->iRun > 0) {
|
|
return drgui_text_engine__move_marker_to_last_character_of_run(pTL, pMarker, pMarker->iRun - 1);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_first_character_of_next_run(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (pTL->runCount > 0 && pMarker->iRun < pTL->runCount - 1) {
|
|
return drgui_text_engine__move_marker_to_first_character_of_run(pTL, pMarker, pMarker->iRun + 1);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__move_marker_to_character(drgui_text_engine* pTL, drgui_text_marker* pMarker, size_t iChar)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
// Clamp the character to the end of the string.
|
|
if (iChar > pTL->textLength) {
|
|
iChar = pTL->textLength;
|
|
}
|
|
|
|
drgui_text_engine__find_run_at_character(pTL, iChar, &pMarker->iRun);
|
|
|
|
assert(pMarker->iRun < pTL->runCount);
|
|
pMarker->iChar = iChar - pTL->pRuns[pMarker->iRun].iChar;
|
|
|
|
|
|
// The relative position depends on whether or not the run is a tab character.
|
|
return drgui_text_engine__update_marker_relative_position(pTL, pMarker);
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__update_marker_relative_position(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
const drgui_text_run* pRun = pTL->pRuns + pMarker->iRun;
|
|
if (pTL->text[pRun->iChar] == '\t')
|
|
{
|
|
const float tabWidth = drgui_text_engine__get_tab_width(pTL);
|
|
|
|
if (pMarker->iChar == 0)
|
|
{
|
|
// Simple case - it's the first tab character which means the relative position is just 0.
|
|
pMarker->relativePosX = 0;
|
|
}
|
|
else
|
|
{
|
|
pMarker->relativePosX = tabWidth * ((pRun->posX + (tabWidth*(pMarker->iChar + 0))) / tabWidth);
|
|
pMarker->relativePosX -= pRun->posX;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return drgui_get_text_cursor_position_from_char(pRun->pFont, pTL->text + pTL->pRuns[pMarker->iRun].iChar, pMarker->iChar, OUT &pMarker->relativePosX);
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__update_marker_sticky_position(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL) {
|
|
return;
|
|
}
|
|
|
|
pMarker->absoluteSickyPosX = pTL->pRuns[pMarker->iRun].posX + pMarker->relativePosX;
|
|
}
|
|
|
|
DRGUI_PRIVATE size_t drgui_text_engine__get_marker_absolute_char_index(drgui_text_engine* pTL, drgui_text_marker* pMarker)
|
|
{
|
|
if (pTL == NULL || pMarker == NULL || pTL->runCount == 0) {
|
|
return 0;
|
|
}
|
|
|
|
return pTL->pRuns[pMarker->iRun].iChar + pMarker->iChar;
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__has_spacing_between_selection_markers(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return (pTL->cursor.iRun != pTL->selectionAnchor.iRun || pTL->cursor.iChar != pTL->selectionAnchor.iChar);
|
|
}
|
|
|
|
DRGUI_PRIVATE size_t drgui_text_engine__split_text_run_by_selection(drgui_text_engine* pTL, drgui_text_run* pRunToSplit, drgui_text_run pSubRunsOut[3])
|
|
{
|
|
if (pTL == NULL || pRunToSplit == NULL || pSubRunsOut == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
drgui_text_marker* pSelectionMarker0 = &pTL->selectionAnchor;
|
|
drgui_text_marker* pSelectionMarker1 = &pTL->cursor;
|
|
if (pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar > pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar)
|
|
{
|
|
drgui_text_marker* temp = pSelectionMarker0;
|
|
pSelectionMarker0 = pSelectionMarker1;
|
|
pSelectionMarker1 = temp;
|
|
}
|
|
|
|
drgui_text_run* pSelectionRun0 = pTL->pRuns + pSelectionMarker0->iRun;
|
|
drgui_text_run* pSelectionRun1 = pTL->pRuns + pSelectionMarker1->iRun;
|
|
|
|
size_t iSelectionChar0 = pSelectionRun0->iChar + pSelectionMarker0->iChar;
|
|
size_t iSelectionChar1 = pSelectionRun1->iChar + pSelectionMarker1->iChar;
|
|
|
|
if (drgui_text_engine_is_anything_selected(pTL))
|
|
{
|
|
if (pRunToSplit->iChar < iSelectionChar1 && pRunToSplit->iCharEnd > iSelectionChar0)
|
|
{
|
|
// The run is somewhere inside the selection region.
|
|
for (int i = 0; i < 3; ++i) {
|
|
pSubRunsOut[i] = *pRunToSplit;
|
|
}
|
|
|
|
if (pRunToSplit->iChar >= iSelectionChar0)
|
|
{
|
|
// The first part of the run is selected.
|
|
if (pRunToSplit->iCharEnd <= iSelectionChar1)
|
|
{
|
|
// The entire run is selected.
|
|
pSubRunsOut[0].backgroundColor = pTL->selectionBackgroundColor;
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
// The head part of the run is selected, and the tail is deselected.
|
|
|
|
// Head.
|
|
pSubRunsOut[0].backgroundColor = pTL->selectionBackgroundColor;
|
|
pSubRunsOut[0].iCharEnd = iSelectionChar1;
|
|
pSubRunsOut[0].width = pSelectionMarker1->relativePosX;
|
|
pSubRunsOut[0].text = pTL->text + pSubRunsOut[0].iChar;
|
|
pSubRunsOut[0].textLength = pSubRunsOut[0].iCharEnd - pSubRunsOut[0].iChar;
|
|
|
|
// Tail.
|
|
pSubRunsOut[1].iChar = iSelectionChar1;
|
|
pSubRunsOut[1].width = pRunToSplit->width - pSelectionMarker1->relativePosX;
|
|
pSubRunsOut[1].posX = pSubRunsOut[0].posX + pSubRunsOut[0].width;
|
|
pSubRunsOut[1].text = pTL->text + pSubRunsOut[1].iChar;
|
|
pSubRunsOut[1].textLength = pSubRunsOut[1].iCharEnd - pSubRunsOut[1].iChar;
|
|
|
|
return 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The first part of the run is deselected. There will be at least 2, but possibly 3 sub-runs in this case.
|
|
if (pRunToSplit->iCharEnd <= iSelectionChar1)
|
|
{
|
|
// The head of the run is deselected and the tail is selected.
|
|
|
|
// Head.
|
|
pSubRunsOut[0].iCharEnd = iSelectionChar0;
|
|
pSubRunsOut[0].width = pSelectionMarker0->relativePosX;
|
|
pSubRunsOut[0].text = pTL->text + pSubRunsOut[0].iChar;
|
|
pSubRunsOut[0].textLength = pSubRunsOut[0].iCharEnd - pSubRunsOut[0].iChar;
|
|
|
|
// Tail.
|
|
pSubRunsOut[1].backgroundColor = pTL->selectionBackgroundColor;
|
|
pSubRunsOut[1].iChar = iSelectionChar0;
|
|
pSubRunsOut[1].width = pRunToSplit->width - pSubRunsOut[0].width;
|
|
pSubRunsOut[1].posX = pSubRunsOut[0].posX + pSubRunsOut[0].width;
|
|
pSubRunsOut[1].text = pTL->text + pSubRunsOut[1].iChar;
|
|
pSubRunsOut[1].textLength = pSubRunsOut[1].iCharEnd - pSubRunsOut[1].iChar;
|
|
|
|
return 2;
|
|
}
|
|
else
|
|
{
|
|
// The head and tail are both deselected, and the middle section is selected.
|
|
|
|
// Head.
|
|
pSubRunsOut[0].iCharEnd = iSelectionChar0;
|
|
pSubRunsOut[0].width = pSelectionMarker0->relativePosX;
|
|
pSubRunsOut[0].text = pTL->text + pSubRunsOut[0].iChar;
|
|
pSubRunsOut[0].textLength = pSubRunsOut[0].iCharEnd - pSubRunsOut[0].iChar;
|
|
|
|
// Mid.
|
|
pSubRunsOut[1].iChar = iSelectionChar0;
|
|
pSubRunsOut[1].iCharEnd = iSelectionChar1;
|
|
pSubRunsOut[1].backgroundColor = pTL->selectionBackgroundColor;
|
|
pSubRunsOut[1].width = pSelectionMarker1->relativePosX - pSelectionMarker0->relativePosX;
|
|
pSubRunsOut[1].posX = pSubRunsOut[0].posX + pSubRunsOut[0].width;
|
|
pSubRunsOut[1].text = pTL->text + pSubRunsOut[1].iChar;
|
|
pSubRunsOut[1].textLength = pSubRunsOut[1].iCharEnd - pSubRunsOut[1].iChar;
|
|
|
|
// Tail.
|
|
pSubRunsOut[2].iChar = iSelectionChar1;
|
|
pSubRunsOut[2].width = pRunToSplit->width - pSelectionMarker1->relativePosX;
|
|
pSubRunsOut[2].posX = pSubRunsOut[1].posX + pSubRunsOut[1].width;
|
|
pSubRunsOut[2].text = pTL->text + pSubRunsOut[2].iChar;
|
|
pSubRunsOut[2].textLength = pSubRunsOut[2].iCharEnd - pSubRunsOut[2].iChar;
|
|
|
|
return 3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we get here it means the run is not within the selected region.
|
|
pSubRunsOut[0] = *pRunToSplit;
|
|
return 1;
|
|
}
|
|
|
|
#if 0
|
|
DRGUI_PRIVATE bool drgui_text_engine__is_run_selected(drgui_text_engine* pTL, unsigned int iRun)
|
|
{
|
|
if (drgui_text_engine_is_anything_selected(pTL))
|
|
{
|
|
drgui_text_marker* pSelectionMarker0;
|
|
drgui_text_marker* pSelectionMarker1;
|
|
if (!drgui_text_engine__get_selection_markers(pTL, &pSelectionMarker0, &pSelectionMarker1)) {
|
|
return false;
|
|
}
|
|
|
|
unsigned int iSelectionChar0 = pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar;
|
|
unsigned int iSelectionChar1 = pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar;
|
|
|
|
return pTL->pRuns[iRun].iChar < iSelectionChar1 && pTL->pRuns[iRun].iCharEnd > iSelectionChar0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__get_selection_markers(drgui_text_engine* pTL, drgui_text_marker** ppSelectionMarker0Out, drgui_text_marker** ppSelectionMarker1Out)
|
|
{
|
|
bool result = false;
|
|
|
|
drgui_text_marker* pSelectionMarker0 = NULL;
|
|
drgui_text_marker* pSelectionMarker1 = NULL;
|
|
if (pTL != NULL && drgui_text_engine_is_anything_selected(pTL))
|
|
{
|
|
pSelectionMarker0 = &pTL->selectionAnchor;
|
|
pSelectionMarker1 = &pTL->cursor;
|
|
if (pTL->pRuns[pSelectionMarker0->iRun].iChar + pSelectionMarker0->iChar > pTL->pRuns[pSelectionMarker1->iRun].iChar + pSelectionMarker1->iChar)
|
|
{
|
|
drgui_text_marker* temp = pSelectionMarker0;
|
|
pSelectionMarker0 = pSelectionMarker1;
|
|
pSelectionMarker1 = temp;
|
|
}
|
|
|
|
result = true;
|
|
}
|
|
|
|
if (ppSelectionMarker0Out) {
|
|
*ppSelectionMarker0Out = pSelectionMarker0;
|
|
}
|
|
if (ppSelectionMarker1Out) {
|
|
*ppSelectionMarker1Out = pSelectionMarker1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__first_line(drgui_text_engine* pTL, drgui_text_engine_line* pLine)
|
|
{
|
|
if (pTL == NULL || pLine == NULL || pTL->runCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
pLine->index = 0;
|
|
pLine->posY = 0;
|
|
pLine->height = 0;
|
|
pLine->iFirstRun = 0;
|
|
pLine->iLastRun = 0;
|
|
|
|
// We need to find the last run in the line and the height. The height is determined by the run with the largest height.
|
|
while (pLine->iLastRun < pTL->runCount)
|
|
{
|
|
if (pLine->height < pTL->pRuns[pLine->iLastRun].height) {
|
|
pLine->height = pTL->pRuns[pLine->iLastRun].height;
|
|
}
|
|
|
|
pLine->iLastRun += 1;
|
|
|
|
if (pTL->pRuns[pLine->iLastRun].iLine != pLine->index) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pLine->iLastRun > 0) {
|
|
pLine->iLastRun -= 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__next_line(drgui_text_engine* pTL, drgui_text_engine_line* pLine)
|
|
{
|
|
if (pTL == NULL || pLine == NULL || pTL->runCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
// If there's no more runs, there is no next line.
|
|
if (pLine->iLastRun == pTL->runCount - 1) {
|
|
return false;
|
|
}
|
|
|
|
pLine->index += 1;
|
|
pLine->posY += pLine->height;
|
|
pLine->height = 0;
|
|
pLine->iFirstRun = pLine->iLastRun + 1;
|
|
pLine->iLastRun = pLine->iFirstRun;
|
|
|
|
while (pLine->iLastRun < pTL->runCount)
|
|
{
|
|
if (pLine->height < pTL->pRuns[pLine->iLastRun].height) {
|
|
pLine->height = pTL->pRuns[pLine->iLastRun].height;
|
|
}
|
|
|
|
pLine->iLastRun += 1;
|
|
|
|
if (pTL->pRuns[pLine->iLastRun].iLine != pLine->index) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pLine->iLastRun > 0) {
|
|
pLine->iLastRun -= 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__trim_undo_stack(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
while (pTL->undoStackCount > pTL->iUndoState)
|
|
{
|
|
unsigned int iLastItem = pTL->undoStackCount - 1;
|
|
|
|
drgui_text_engine__uninit_undo_state(pTL->pUndoStack + iLastItem);
|
|
pTL->undoStackCount -= 1;
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE bool drgui_text_engine__diff_states(drgui_text_engine_state* pPrevState, drgui_text_engine_state* pCurrentState, drgui_text_engine_undo_state* pUndoStateOut)
|
|
{
|
|
if (pPrevState == NULL || pCurrentState == NULL || pUndoStateOut == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (pPrevState->text == NULL || pCurrentState->text == NULL) {
|
|
return false;
|
|
}
|
|
|
|
const char* prevText = pPrevState->text;
|
|
const char* currText = pCurrentState->text;
|
|
|
|
const size_t prevLen = strlen(prevText);
|
|
const size_t currLen = strlen(currText);
|
|
|
|
|
|
// The first step is to find the position of the first differing character.
|
|
size_t sameChCountStart;
|
|
for (sameChCountStart = 0; sameChCountStart < prevLen && sameChCountStart < currLen; ++sameChCountStart)
|
|
{
|
|
char prevCh = prevText[sameChCountStart];
|
|
char currCh = currText[sameChCountStart];
|
|
|
|
if (prevCh != currCh) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// The next step is to find the position of the last differing character.
|
|
size_t sameChCountEnd;
|
|
for (sameChCountEnd = 0; sameChCountEnd < prevLen && sameChCountEnd < currLen; ++sameChCountEnd)
|
|
{
|
|
// Don't move beyond the first differing character.
|
|
if (prevLen - sameChCountEnd <= sameChCountStart ||
|
|
currLen - sameChCountEnd <= sameChCountStart)
|
|
{
|
|
break;
|
|
}
|
|
|
|
char prevCh = prevText[prevLen - sameChCountEnd - 1];
|
|
char currCh = currText[currLen - sameChCountEnd - 1];
|
|
|
|
if (prevCh != currCh) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// At this point we know which section of the text is different. We now need to initialize the undo state object.
|
|
pUndoStateOut->diffPos = sameChCountStart;
|
|
pUndoStateOut->newState = *pCurrentState;
|
|
pUndoStateOut->newState.text = NULL;
|
|
pUndoStateOut->oldState = *pPrevState;
|
|
pUndoStateOut->oldState.text = NULL;
|
|
|
|
size_t oldTextLen = prevLen - sameChCountStart - sameChCountEnd;
|
|
pUndoStateOut->oldText = (char*)malloc(oldTextLen + 1);
|
|
drgui__strncpy_s(pUndoStateOut->oldText, oldTextLen + 1, prevText + sameChCountStart, oldTextLen);
|
|
|
|
size_t newTextLen = currLen - sameChCountStart - sameChCountEnd;
|
|
pUndoStateOut->newText = (char*)malloc(newTextLen + 1);
|
|
drgui__strncpy_s(pUndoStateOut->newText, newTextLen + 1, currText + sameChCountStart, newTextLen);
|
|
|
|
return true;
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__uninit_undo_state(drgui_text_engine_undo_state* pUndoState)
|
|
{
|
|
if (pUndoState == NULL) {
|
|
return;
|
|
}
|
|
|
|
free(pUndoState->oldText);
|
|
pUndoState->oldText = NULL;
|
|
|
|
free(pUndoState->newText);
|
|
pUndoState->newText = NULL;
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__push_undo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState)
|
|
{
|
|
if (pTL == NULL || pUndoState == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pTL->iUndoState == pTL->undoStackCount);
|
|
|
|
|
|
drgui_text_engine_undo_state* pOldStack = pTL->pUndoStack;
|
|
drgui_text_engine_undo_state* pNewStack = (drgui_text_engine_undo_state*)malloc(sizeof(*pNewStack) * (pTL->undoStackCount + 1));
|
|
|
|
if (pTL->undoStackCount > 0) {
|
|
memcpy(pNewStack, pOldStack, sizeof(*pNewStack) * (pTL->undoStackCount));
|
|
}
|
|
|
|
pNewStack[pTL->undoStackCount] = *pUndoState;
|
|
pTL->pUndoStack = pNewStack;
|
|
pTL->undoStackCount += 1;
|
|
pTL->iUndoState += 1;
|
|
|
|
if (pTL->onUndoPointChanged) {
|
|
pTL->onUndoPointChanged(pTL, pTL->iUndoState);
|
|
}
|
|
|
|
free(pOldStack);
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__apply_undo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState)
|
|
{
|
|
if (pTL == NULL || pUndoState == NULL) {
|
|
return;
|
|
}
|
|
|
|
// When undoing we want to remove the new text and replace it with the old text.
|
|
|
|
size_t iFirstCh = pUndoState->diffPos;
|
|
size_t iLastChPlus1 = pUndoState->diffPos + strlen(pUndoState->newText);
|
|
size_t bytesToRemove = iLastChPlus1 - iFirstCh;
|
|
if (bytesToRemove > 0)
|
|
{
|
|
memmove(pTL->text + iFirstCh, pTL->text + iLastChPlus1, pTL->textLength - iLastChPlus1);
|
|
pTL->textLength -= bytesToRemove;
|
|
pTL->text[pTL->textLength] = '\0';
|
|
}
|
|
|
|
// TODO: This needs improving because it results in multiple onTextChanged and onDirty events being posted.
|
|
|
|
// Insert the old text.
|
|
drgui_text_engine_insert_text(pTL, pUndoState->oldText, pUndoState->diffPos);
|
|
|
|
|
|
// The layout will have changed so it needs to be refreshed.
|
|
drgui_text_engine__refresh(pTL);
|
|
|
|
|
|
// Markers needs to be updated after refreshing the layout.
|
|
drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, pUndoState->oldState.cursorPos);
|
|
drgui_text_engine__move_marker_to_character(pTL, &pTL->selectionAnchor, pUndoState->oldState.selectionAnchorPos);
|
|
|
|
// The cursor's sticky position needs to be updated whenever the text is edited.
|
|
drgui_text_engine__update_marker_sticky_position(pTL, &pTL->cursor);
|
|
|
|
// Ensure we mark the text as selected if appropriate.
|
|
pTL->isAnythingSelected = pUndoState->oldState.isAnythingSelected;
|
|
|
|
|
|
if (pTL->onTextChanged) {
|
|
pTL->onTextChanged(pTL);
|
|
}
|
|
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
|
|
if (pTL->onDirty) {
|
|
pTL->onDirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__apply_redo_state(drgui_text_engine* pTL, drgui_text_engine_undo_state* pUndoState)
|
|
{
|
|
if (pTL == NULL || pUndoState == NULL) {
|
|
return;
|
|
}
|
|
|
|
// An redo is just the opposite of an undo. We want to remove the old text and replace it with the new text.
|
|
|
|
size_t iFirstCh = pUndoState->diffPos;
|
|
size_t iLastChPlus1 = pUndoState->diffPos + strlen(pUndoState->oldText);
|
|
size_t bytesToRemove = iLastChPlus1 - iFirstCh;
|
|
if (bytesToRemove > 0)
|
|
{
|
|
memmove(pTL->text + iFirstCh, pTL->text + iLastChPlus1, pTL->textLength - iLastChPlus1);
|
|
pTL->textLength -= bytesToRemove;
|
|
pTL->text[pTL->textLength] = '\0';
|
|
}
|
|
|
|
// TODO: This needs improving because it results in multiple onTextChanged and onDirty events being posted.
|
|
|
|
// Insert the new text.
|
|
drgui_text_engine_insert_text(pTL, pUndoState->newText, pUndoState->diffPos);
|
|
|
|
|
|
// The layout will have changed so it needs to be refreshed.
|
|
drgui_text_engine__refresh(pTL);
|
|
|
|
|
|
// Markers needs to be updated after refreshing the layout.
|
|
drgui_text_engine__move_marker_to_character(pTL, &pTL->cursor, pUndoState->newState.cursorPos);
|
|
drgui_text_engine__move_marker_to_character(pTL, &pTL->selectionAnchor, pUndoState->newState.selectionAnchorPos);
|
|
|
|
// The cursor's sticky position needs to be updated whenever the text is edited.
|
|
drgui_text_engine__update_marker_sticky_position(pTL, &pTL->cursor);
|
|
|
|
// Ensure we mark the text as selected if appropriate.
|
|
pTL->isAnythingSelected = pUndoState->newState.isAnythingSelected;
|
|
|
|
|
|
if (pTL->onTextChanged) {
|
|
pTL->onTextChanged(pTL);
|
|
}
|
|
|
|
drgui_text_engine__on_cursor_move(pTL);
|
|
|
|
if (pTL->onDirty) {
|
|
pTL->onDirty(pTL, drgui_text_engine__local_rect(pTL));
|
|
}
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE drgui_rect drgui_text_engine__local_rect(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return drgui_make_rect(0, 0, 0, 0);
|
|
}
|
|
|
|
return drgui_make_rect(0, 0, pTL->containerWidth, pTL->containerHeight);
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__on_cursor_move(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
// When the cursor moves we want to reset the cursor's blink state.
|
|
pTL->timeToNextCursorBlink = pTL->cursorBlinkRate;
|
|
pTL->isCursorBlinkOn = true;
|
|
|
|
if (pTL->onCursorMove) {
|
|
pTL->onCursorMove(pTL);
|
|
}
|
|
|
|
drgui_text_engine__on_dirty(pTL, drgui_text_engine_get_cursor_rect(pTL));
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__on_dirty(drgui_text_engine* pTL, drgui_rect rect)
|
|
{
|
|
drgui_text_engine__begin_dirty(pTL);
|
|
{
|
|
pTL->accumulatedDirtyRect = drgui_rect_union(pTL->accumulatedDirtyRect, rect);
|
|
}
|
|
drgui_text_engine__end_dirty(pTL);
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__begin_dirty(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTL->dirtyCounter += 1;
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_text_engine__end_dirty(drgui_text_engine* pTL)
|
|
{
|
|
if (pTL == NULL) {
|
|
return;
|
|
}
|
|
|
|
assert(pTL->dirtyCounter > 0);
|
|
|
|
pTL->dirtyCounter -= 1;
|
|
|
|
if (pTL->dirtyCounter == 0) {
|
|
if (pTL->onDirty) {
|
|
pTL->onDirty(pTL, pTL->accumulatedDirtyRect);
|
|
}
|
|
|
|
pTL->accumulatedDirtyRect = drgui_make_inside_out_rect();
|
|
}
|
|
}
|
|
#endif //DR_GUI_IMPLEMENTATION
|
|
#endif
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Scrollbar
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef drgui_scrollbar_h
|
|
#define drgui_scrollbar_h
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef enum
|
|
{
|
|
drgui_sb_orientation_none,
|
|
drgui_sb_orientation_vertical,
|
|
drgui_sb_orientation_horizontal
|
|
|
|
} drgui_sb_orientation;
|
|
|
|
typedef void (* drgui_sb_on_scroll_proc)(drgui_element* pSBElement, int scrollPos);
|
|
|
|
|
|
/// Creates a scrollbar element.
|
|
drgui_element* drgui_create_scrollbar(drgui_context* pContext, drgui_element* pParent, drgui_sb_orientation orientation, size_t extraDataSize, const void* pExtraData);
|
|
|
|
/// Deletes the given scrollbar element.
|
|
void drgui_delete_scrollbar(drgui_element* pSBElement);
|
|
|
|
|
|
/// Retrieves the size of the extra data associated with the scrollbar.
|
|
size_t drgui_sb_get_extra_data_size(drgui_element* pSBElement);
|
|
|
|
/// Retrieves a pointer to the extra data associated with the scrollbar.
|
|
void* drgui_sb_get_extra_data(drgui_element* pSBElement);
|
|
|
|
|
|
/// Retrieves the orientation of the given scrollbar.
|
|
drgui_sb_orientation drgui_sb_get_orientation(drgui_element* pSBElement);
|
|
|
|
|
|
/// Sets the given scrollbar's range.
|
|
void drgui_sb_set_range(drgui_element* pSBElement, int rangeMin, int rangeMax);
|
|
|
|
/// Retrieves the given scrollbar's range.
|
|
void drgui_sb_get_range(drgui_element* pSBElement, int* pRangeMinOut, int* pRangeMaxOut);
|
|
|
|
|
|
/// Sets the page size of the given scrollbar's page.
|
|
void drgui_sb_set_page_size(drgui_element* pSBElement, int pageSize);
|
|
|
|
/// Retrieves the page size of the given scrollbar's page.
|
|
int drgui_sb_get_page_size(drgui_element* pSBElement);
|
|
|
|
|
|
/// Sets the range and page size.
|
|
///
|
|
/// @remarks
|
|
/// Use this when both the range and page size need to be updated at the same time.
|
|
void drgui_sb_set_range_and_page_size(drgui_element* pSBElement, int rangeMin, int rangeMax, int pageSize);
|
|
|
|
|
|
/// Explicitly sets the scroll position.
|
|
///
|
|
/// @remarks
|
|
/// This will move the thumb, but not post the on_scroll event.
|
|
/// @par
|
|
/// The scroll position will be clamped to the current range, minus the page size.
|
|
void drgui_sb_set_scroll_position(drgui_element* pSBElement, int position);
|
|
|
|
/// Retrieves the scroll position.
|
|
int drgui_sb_get_scroll_position(drgui_element* pSBElement);
|
|
|
|
|
|
/// Scrolls by the given amount.
|
|
///
|
|
/// @remarks
|
|
/// If the resulting scroll position differs from the old one, the on on_scroll event will be posted.
|
|
void drgui_sb_scroll(drgui_element* pSBElement, int offset);
|
|
|
|
/// Scrolls to the given position.
|
|
///
|
|
/// @remarks
|
|
/// This differs from drgui_sb_set_scroll_position in that it will post the on_scroll event.
|
|
/// @par
|
|
/// Note that the actual maximum scrollable position is equal to the maximum range value minus the page size.
|
|
void drgui_sb_scroll_to(drgui_element* pSBElement, int newScrollPos);
|
|
|
|
|
|
/// Enables auto-hiding of the thumb.
|
|
void drgui_sb_enable_thumb_auto_hide(drgui_element* pSBElement);
|
|
|
|
/// Disables auto-hiding of the thumb.
|
|
void drgui_sb_disable_thumb_auto_hide(drgui_element* pSBElement);
|
|
|
|
/// Determines whether or not thumb auto-hiding is enabled.
|
|
bool drgui_sb_is_thumb_auto_hide_enabled(drgui_element* pSBElement);
|
|
|
|
/// Determines whether or not the thumb is visible.
|
|
///
|
|
/// @remarks
|
|
/// This is determined by whether or not the thumb is set to auto-hide and the current range and page size.
|
|
bool drgui_sb_is_thumb_visible(drgui_element* pSBElement);
|
|
|
|
|
|
/// Sets the mouse wheel scale.
|
|
///
|
|
/// @remarks
|
|
/// Set this to a negative value to reverse the direction.
|
|
void drgui_sb_set_mouse_wheel_scele(drgui_element* pSBElement, int scale);
|
|
|
|
/// Retrieves the mouse wheel scale.
|
|
int drgui_sb_get_mouse_wheel_scale(drgui_element* pSBElement);
|
|
|
|
|
|
/// Sets the color of the track.
|
|
void drgui_sb_set_track_color(drgui_element* pSBElement, drgui_color color);
|
|
|
|
/// Sets the default color of the thumb.
|
|
void drgui_sb_set_default_thumb_color(drgui_element* pSBElement, drgui_color color);
|
|
|
|
/// Sets the hovered color of the thumb.
|
|
void drgui_sb_set_hovered_thumb_color(drgui_element* pSBElement, drgui_color color);
|
|
|
|
/// Sets the pressed color of the thumb.
|
|
void drgui_sb_set_pressed_thumb_color(drgui_element* pSBElement, drgui_color color);
|
|
|
|
|
|
/// Sets the function to call when the given scrollbar is scrolled.
|
|
void drgui_sb_set_on_scroll(drgui_element* pSBElement, drgui_sb_on_scroll_proc onScroll);
|
|
|
|
/// Retrieves the function call when the given scrollbar is scrolled.
|
|
drgui_sb_on_scroll_proc drgui_sb_get_on_scroll(drgui_element* pSBElement);
|
|
|
|
|
|
/// Calculates the relative rectangle of the given scrollbar's thumb.
|
|
drgui_rect drgui_sb_get_thumb_rect(drgui_element* pSBElement);
|
|
|
|
|
|
/// Called when the size event needs to be processed for the given scrollbar.
|
|
void drgui_sb_on_size(drgui_element* pSBElement, float newWidth, float newHeight);
|
|
|
|
/// Called when the mouse leave event needs to be processed for the given scrollbar.
|
|
void drgui_sb_on_mouse_leave(drgui_element* pSBElement);
|
|
|
|
/// Called when the mouse move event needs to be processed for the given scrollbar.
|
|
void drgui_sb_on_mouse_move(drgui_element* pSBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the mouse button down event needs to be processed for the given scrollbar.
|
|
void drgui_sb_on_mouse_button_down(drgui_element* pSBElement, int button, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the mouse button up event needs to be processed for the given scrollbar.
|
|
void drgui_sb_on_mouse_button_up(drgui_element* pSBElement, int button, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the mouse wheel event needs to be processed for the given scrollbar.
|
|
void drgui_sb_on_mouse_wheel(drgui_element* pSBElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the paint event needs to be processed.
|
|
void drgui_sb_on_paint(drgui_element* pSBElement, drgui_rect relativeClippingRect, void* pPaintData);
|
|
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif //drgui_scrollbar_h
|
|
|
|
|
|
#ifdef DR_GUI_IMPLEMENTATION
|
|
|
|
#define DRGUI_MIN_SCROLLBAR_THUMB_SIZE 16
|
|
|
|
typedef struct
|
|
{
|
|
/// The orientation.
|
|
drgui_sb_orientation orientation;
|
|
|
|
/// The minimum scroll range.
|
|
int rangeMin;
|
|
|
|
/// The maximum scroll range.
|
|
int rangeMax;
|
|
|
|
/// The page size.
|
|
int pageSize;
|
|
|
|
/// The current scroll position.
|
|
int scrollPos;
|
|
|
|
/// Whether or not to auto-hide the thumb.
|
|
bool autoHideThumb;
|
|
|
|
/// The mouse wheel scale.
|
|
int mouseWheelScale;
|
|
|
|
/// The color of the track.
|
|
drgui_color trackColor;
|
|
|
|
/// The color of the thumb while not hovered or pressed.
|
|
drgui_color thumbColor;
|
|
|
|
/// The color of the thumb while hovered.
|
|
drgui_color thumbColorHovered;
|
|
|
|
/// The color of the thumb while pressed.
|
|
drgui_color thumbColorPressed;
|
|
|
|
/// The function to call when the scroll position changes.
|
|
drgui_sb_on_scroll_proc onScroll;
|
|
|
|
|
|
/// The current size of the thumb.
|
|
float thumbSize;
|
|
|
|
/// The current position of the thumb.
|
|
float thumbPos;
|
|
|
|
/// The amount of padding between the edge of the scrollbar and the thumb.
|
|
float thumbPadding;
|
|
|
|
/// Whether or not we are hovered over the thumb.
|
|
bool thumbHovered;
|
|
|
|
/// Whether or not the thumb is pressed.
|
|
bool thumbPressed;
|
|
|
|
/// The relative position of the mouse on the x axis at the time the thumb was pressed with the mouse.
|
|
float thumbClickPosX;
|
|
|
|
/// The relative position of the mouse on the y axis at the time the thumb was pressed with the mouse.
|
|
float thumbClickPosY;
|
|
|
|
|
|
/// The size of the extra data.
|
|
size_t extraDataSize;
|
|
|
|
/// A pointer to the extra data.
|
|
char pExtraData[1];
|
|
|
|
} drgui_scrollbar;
|
|
|
|
|
|
/// Refreshes the given scrollbar's thumb layout and redraws it.
|
|
DRGUI_PRIVATE void drgui_sb_refresh_thumb(drgui_element* pSBElement);
|
|
|
|
/// Calculates the size of the thumb. This does not change the state of the thumb.
|
|
DRGUI_PRIVATE float drgui_sb_calculate_thumb_size(drgui_element* pSBElement);
|
|
|
|
/// Calculates the position of the thumb. This does not change the state of the thumb.
|
|
DRGUI_PRIVATE float drgui_sb_calculate_thumb_position(drgui_element* pSBElement);
|
|
|
|
/// Retrieves the size of the given scrollbar's track. For vertical alignments, it's the height of the element, otherwise it's the width.
|
|
DRGUI_PRIVATE float drgui_sb_get_track_size(drgui_element* pSBElement);
|
|
|
|
/// Makes the given point that's relative to the given scrollbar relative to it's thumb.
|
|
DRGUI_PRIVATE void drgui_sb_make_relative_to_thumb(drgui_element* pSBElement, float* pPosX, float* pPosY);
|
|
|
|
/// Calculates the scroll position based on the current position of the thumb. This is used for scrolling while dragging the thumb.
|
|
DRGUI_PRIVATE int drgui_sb_calculate_scroll_pos_from_thumb_pos(drgui_element* pScrollba, float thumbPosr);
|
|
|
|
/// Simple clamp function.
|
|
DRGUI_PRIVATE float drgui_sb_clampf(float n, float lower, float upper)
|
|
{
|
|
return n <= lower ? lower : n >= upper ? upper : n;
|
|
}
|
|
|
|
/// Simple clamp function.
|
|
DRGUI_PRIVATE int drgui_sb_clampi(int n, int lower, int upper)
|
|
{
|
|
return n <= lower ? lower : n >= upper ? upper : n;
|
|
}
|
|
|
|
/// Simple max function.
|
|
DRGUI_PRIVATE int drgui_sb_maxi(int x, int y)
|
|
{
|
|
return (x > y) ? x : y;
|
|
}
|
|
|
|
|
|
drgui_element* drgui_create_scrollbar(drgui_context* pContext, drgui_element* pParent, drgui_sb_orientation orientation, size_t extraDataSize, const void* pExtraData)
|
|
{
|
|
if (pContext == NULL || orientation == drgui_sb_orientation_none) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_element* pSBElement = drgui_create_element(pContext, pParent, sizeof(drgui_scrollbar) + extraDataSize, NULL);
|
|
if (pSBElement == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
assert(pSB != NULL);
|
|
|
|
pSB->orientation = orientation;
|
|
pSB->rangeMin = 0;
|
|
pSB->rangeMax = 0;
|
|
pSB->pageSize = 0;
|
|
pSB->scrollPos = 0;
|
|
pSB->autoHideThumb = true;
|
|
pSB->mouseWheelScale = 1;
|
|
pSB->trackColor = drgui_rgb(80, 80, 80);
|
|
pSB->thumbColor = drgui_rgb(112, 112, 112);
|
|
pSB->thumbColorHovered = drgui_rgb(144, 144, 144);
|
|
pSB->thumbColorPressed = drgui_rgb(180, 180, 180);
|
|
pSB->onScroll = NULL;
|
|
|
|
pSB->thumbSize = DRGUI_MIN_SCROLLBAR_THUMB_SIZE;
|
|
pSB->thumbPos = 0;
|
|
pSB->thumbPadding = 2;
|
|
pSB->thumbHovered = false;
|
|
pSB->thumbPressed = false;
|
|
pSB->thumbClickPosX = 0;
|
|
pSB->thumbClickPosY = 0;
|
|
|
|
pSB->extraDataSize = extraDataSize;
|
|
if (pExtraData != NULL) {
|
|
memcpy(pSB->pExtraData, pExtraData, extraDataSize);
|
|
}
|
|
|
|
|
|
// Default event handlers.
|
|
drgui_set_on_size(pSBElement, drgui_sb_on_size);
|
|
drgui_set_on_mouse_leave(pSBElement, drgui_sb_on_mouse_leave);
|
|
drgui_set_on_mouse_move(pSBElement, drgui_sb_on_mouse_move);
|
|
drgui_set_on_mouse_button_down(pSBElement, drgui_sb_on_mouse_button_down);
|
|
drgui_set_on_mouse_button_up(pSBElement, drgui_sb_on_mouse_button_up);
|
|
drgui_set_on_mouse_wheel(pSBElement, drgui_sb_on_mouse_wheel);
|
|
drgui_set_on_paint(pSBElement, drgui_sb_on_paint);
|
|
|
|
|
|
return pSBElement;
|
|
}
|
|
|
|
void drgui_delete_scrollbar(drgui_element* pSBElement)
|
|
{
|
|
if (pSBElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_delete_element(pSBElement);
|
|
}
|
|
|
|
|
|
size_t drgui_sb_get_extra_data_size(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pSB->extraDataSize;
|
|
}
|
|
|
|
void* drgui_sb_get_extra_data(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pSB->pExtraData;
|
|
}
|
|
|
|
|
|
drgui_sb_orientation drgui_sb_get_orientation(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return drgui_sb_orientation_none;
|
|
}
|
|
|
|
return pSB->orientation;
|
|
}
|
|
|
|
|
|
void drgui_sb_set_range(drgui_element* pSBElement, int rangeMin, int rangeMax)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pSB->rangeMin = rangeMin;
|
|
pSB->rangeMax = rangeMax;
|
|
|
|
|
|
// Make sure the scroll position is still valid.
|
|
drgui_sb_scroll_to(pSBElement, drgui_sb_get_scroll_position(pSBElement));
|
|
|
|
// The thumb may have changed, so refresh it.
|
|
drgui_sb_refresh_thumb(pSBElement);
|
|
}
|
|
|
|
void drgui_sb_get_range(drgui_element* pSBElement, int* pRangeMinOut, int* pRangeMaxOut)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pRangeMinOut != NULL) {
|
|
*pRangeMinOut = pSB->rangeMin;
|
|
}
|
|
|
|
if (pRangeMaxOut != NULL) {
|
|
*pRangeMaxOut = pSB->rangeMax;
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_sb_set_page_size(drgui_element* pSBElement, int pageSize)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pSB->pageSize = pageSize;
|
|
|
|
|
|
// Make sure the scroll position is still valid.
|
|
drgui_sb_scroll_to(pSBElement, drgui_sb_get_scroll_position(pSBElement));
|
|
|
|
// The thumb may have changed, so refresh it.
|
|
drgui_sb_refresh_thumb(pSBElement);
|
|
}
|
|
|
|
int drgui_sb_get_page_size(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pSB->pageSize;
|
|
}
|
|
|
|
|
|
void drgui_sb_set_range_and_page_size(drgui_element* pSBElement, int rangeMin, int rangeMax, int pageSize)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pSB->rangeMin = rangeMin;
|
|
pSB->rangeMax = rangeMax;
|
|
pSB->pageSize = pageSize;
|
|
|
|
|
|
// Make sure the scroll position is still valid.
|
|
drgui_sb_scroll_to(pSBElement, drgui_sb_get_scroll_position(pSBElement));
|
|
|
|
// The thumb may have changed, so refresh it.
|
|
drgui_sb_refresh_thumb(pSBElement);
|
|
}
|
|
|
|
|
|
void drgui_sb_set_scroll_position(drgui_element* pSBElement, int position)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
int newScrollPos = drgui_sb_clampi(position, pSB->rangeMin, drgui_sb_maxi(0, pSB->rangeMax - pSB->pageSize + 1));
|
|
if (newScrollPos != pSB->scrollPos)
|
|
{
|
|
pSB->scrollPos = newScrollPos;
|
|
|
|
// The position of the thumb has changed, so refresh it.
|
|
drgui_sb_refresh_thumb(pSBElement);
|
|
}
|
|
}
|
|
|
|
int drgui_sb_get_scroll_position(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pSB->scrollPos;
|
|
}
|
|
|
|
|
|
void drgui_sb_scroll(drgui_element* pSBElement, int offset)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_sb_scroll_to(pSBElement, pSB->scrollPos + offset);
|
|
}
|
|
|
|
void drgui_sb_scroll_to(drgui_element* pSBElement, int newScrollPos)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
int oldScrollPos = pSB->scrollPos;
|
|
drgui_sb_set_scroll_position(pSBElement, newScrollPos);
|
|
|
|
if (oldScrollPos != pSB->scrollPos)
|
|
{
|
|
if (pSB->onScroll) {
|
|
pSB->onScroll(pSBElement, pSB->scrollPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_sb_enable_thumb_auto_hide(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pSB->autoHideThumb != true)
|
|
{
|
|
pSB->autoHideThumb = true;
|
|
|
|
// The thumb needs to be refreshed in order to show the correct state.
|
|
drgui_sb_refresh_thumb(pSBElement);
|
|
}
|
|
}
|
|
|
|
void drgui_sb_disable_thumb_auto_hide(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pSB->autoHideThumb != false)
|
|
{
|
|
pSB->autoHideThumb = false;
|
|
|
|
// The thumb needs to be refreshed in order to show the correct state.
|
|
drgui_sb_refresh_thumb(pSBElement);
|
|
}
|
|
}
|
|
|
|
bool drgui_sb_is_thumb_auto_hide_enabled(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pSB->autoHideThumb;
|
|
}
|
|
|
|
bool drgui_sb_is_thumb_visible(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Always visible if auto-hiding is disabled.
|
|
if (!pSB->autoHideThumb) {
|
|
return true;
|
|
}
|
|
|
|
return pSB->pageSize < (pSB->rangeMax - pSB->rangeMin + 1) && pSB->pageSize > 0;
|
|
}
|
|
|
|
|
|
void drgui_sb_set_mouse_wheel_scele(drgui_element* pSBElement, int scale)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pSB->mouseWheelScale = scale;
|
|
}
|
|
|
|
int drgui_sb_get_mouse_wheel_scale(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
return pSB->mouseWheelScale;
|
|
}
|
|
|
|
|
|
void drgui_sb_set_track_color(drgui_element* pSBElement, drgui_color color)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pSB->trackColor = color;
|
|
}
|
|
|
|
void drgui_sb_set_default_thumb_color(drgui_element* pSBElement, drgui_color color)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pSB->thumbColor = color;
|
|
}
|
|
|
|
void drgui_sb_set_hovered_thumb_color(drgui_element* pSBElement, drgui_color color)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pSB->thumbColorHovered = color;
|
|
}
|
|
|
|
void drgui_sb_set_pressed_thumb_color(drgui_element* pSBElement, drgui_color color)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pSB->thumbColorPressed = color;
|
|
}
|
|
|
|
|
|
void drgui_sb_set_on_scroll(drgui_element* pSBElement, drgui_sb_on_scroll_proc onScroll)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pSB->onScroll = onScroll;
|
|
}
|
|
|
|
drgui_sb_on_scroll_proc drgui_sb_get_on_scroll(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pSB->onScroll;
|
|
}
|
|
|
|
|
|
drgui_rect drgui_sb_get_thumb_rect(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return drgui_make_rect(0, 0, 0, 0);
|
|
}
|
|
|
|
drgui_rect rect = {0, 0, 0, 0};
|
|
rect.left = pSB->thumbPadding;
|
|
rect.top = pSB->thumbPadding;
|
|
|
|
if (pSB->orientation == drgui_sb_orientation_vertical)
|
|
{
|
|
// Vertical.
|
|
rect.left = pSB->thumbPadding;
|
|
rect.right = drgui_get_width(pSBElement) - pSB->thumbPadding;
|
|
rect.top = pSB->thumbPadding + pSB->thumbPos;
|
|
rect.bottom = rect.top + pSB->thumbSize;
|
|
}
|
|
else
|
|
{
|
|
// Horizontal.
|
|
rect.left = pSB->thumbPadding + pSB->thumbPos;
|
|
rect.right = rect.left + pSB->thumbSize;
|
|
rect.top = pSB->thumbPadding;
|
|
rect.bottom = drgui_get_height(pSBElement) - pSB->thumbPadding;
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
|
|
void drgui_sb_on_size(drgui_element* pSBElement, float newWidth, float newHeight)
|
|
{
|
|
(void)newWidth;
|
|
(void)newHeight;
|
|
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_sb_refresh_thumb(pSBElement);
|
|
}
|
|
|
|
void drgui_sb_on_mouse_leave(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
bool needsRedraw = false;
|
|
if (pSB->thumbHovered)
|
|
{
|
|
needsRedraw = true;
|
|
pSB->thumbHovered = false;
|
|
}
|
|
|
|
if (pSB->thumbPressed)
|
|
{
|
|
needsRedraw = true;
|
|
pSB->thumbPressed = false;
|
|
}
|
|
|
|
if (needsRedraw) {
|
|
drgui_dirty(pSBElement, drgui_sb_get_thumb_rect(pSBElement));
|
|
}
|
|
}
|
|
|
|
void drgui_sb_on_mouse_move(drgui_element* pSBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)stateFlags;
|
|
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pSB->thumbPressed)
|
|
{
|
|
// The thumb is pressed. Drag it.
|
|
float thumbRelativeMousePosX = (float)relativeMousePosX;
|
|
float thumbRelativeMousePosY = (float)relativeMousePosY;
|
|
drgui_sb_make_relative_to_thumb(pSBElement, &thumbRelativeMousePosX, &thumbRelativeMousePosY);
|
|
|
|
float dragOffsetX = thumbRelativeMousePosX - pSB->thumbClickPosX;
|
|
float dragOffsetY = thumbRelativeMousePosY - pSB->thumbClickPosY;
|
|
|
|
float destTrackPos = pSB->thumbPos;
|
|
if (pSB->orientation == drgui_sb_orientation_vertical) {
|
|
destTrackPos += dragOffsetY;
|
|
} else {
|
|
destTrackPos += dragOffsetX;
|
|
}
|
|
|
|
int destScrollPos = drgui_sb_calculate_scroll_pos_from_thumb_pos(pSBElement, destTrackPos);
|
|
if (destScrollPos != pSB->scrollPos)
|
|
{
|
|
drgui_sb_scroll_to(pSBElement, destScrollPos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The thumb is not pressed. We just need to check if the hovered state has change and redraw if required.
|
|
if (drgui_sb_is_thumb_visible(pSBElement))
|
|
{
|
|
bool wasThumbHovered = pSB->thumbHovered;
|
|
|
|
drgui_rect thumbRect = drgui_sb_get_thumb_rect(pSBElement);
|
|
pSB->thumbHovered = drgui_rect_contains_point(thumbRect, (float)relativeMousePosX, (float)relativeMousePosY);
|
|
|
|
if (wasThumbHovered != pSB->thumbHovered) {
|
|
drgui_dirty(pSBElement, thumbRect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_sb_on_mouse_button_down(drgui_element* pSBElement, int button, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)stateFlags;
|
|
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (button == DRGUI_MOUSE_BUTTON_LEFT)
|
|
{
|
|
if (drgui_sb_is_thumb_visible(pSBElement))
|
|
{
|
|
drgui_rect thumbRect = drgui_sb_get_thumb_rect(pSBElement);
|
|
if (drgui_rect_contains_point(thumbRect, (float)relativeMousePosX, (float)relativeMousePosY))
|
|
{
|
|
if (!pSB->thumbPressed)
|
|
{
|
|
drgui_capture_mouse(pSBElement);
|
|
pSB->thumbPressed = true;
|
|
|
|
pSB->thumbClickPosX = (float)relativeMousePosX;
|
|
pSB->thumbClickPosY = (float)relativeMousePosY;
|
|
drgui_sb_make_relative_to_thumb(pSBElement, &pSB->thumbClickPosX, &pSB->thumbClickPosY);
|
|
|
|
drgui_dirty(pSBElement, drgui_sb_get_thumb_rect(pSBElement));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the click position is above the thumb we want to scroll up by a page. If it's below the thumb, we scroll down by a page.
|
|
if (relativeMousePosY < thumbRect.top) {
|
|
drgui_sb_scroll(pSBElement, -drgui_sb_get_page_size(pSBElement));
|
|
} else if (relativeMousePosY >= thumbRect.bottom) {
|
|
drgui_sb_scroll(pSBElement, drgui_sb_get_page_size(pSBElement));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_sb_on_mouse_button_up(drgui_element* pSBElement, int button, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)relativeMousePosX;
|
|
(void)relativeMousePosY;
|
|
(void)stateFlags;
|
|
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (button == DRGUI_MOUSE_BUTTON_LEFT)
|
|
{
|
|
if (pSB->thumbPressed && drgui_get_element_with_mouse_capture(pSBElement->pContext) == pSBElement)
|
|
{
|
|
drgui_release_mouse(pSBElement->pContext);
|
|
pSB->thumbPressed = false;
|
|
|
|
drgui_dirty(pSBElement, drgui_sb_get_thumb_rect(pSBElement));
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_sb_on_mouse_wheel(drgui_element* pSBElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)relativeMousePosX;
|
|
(void)relativeMousePosY;
|
|
(void)stateFlags;
|
|
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_sb_scroll(pSBElement, -delta * drgui_sb_get_mouse_wheel_scale(pSBElement));
|
|
}
|
|
|
|
void drgui_sb_on_paint(drgui_element* pSBElement, drgui_rect relativeClippingRect, void* pPaintData)
|
|
{
|
|
(void)relativeClippingRect;
|
|
|
|
const drgui_scrollbar* pSB = (const drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_rect thumbRect = drgui_sb_get_thumb_rect(pSBElement);
|
|
|
|
if (drgui_sb_is_thumb_visible(pSBElement))
|
|
{
|
|
// The thumb is visible.
|
|
|
|
// Track. We draw this in 4 seperate pieces so we can avoid overdraw with the thumb.
|
|
drgui_draw_rect(pSBElement, drgui_make_rect(0, 0, drgui_get_width(pSBElement), thumbRect.top), pSB->trackColor, pPaintData); // Top
|
|
drgui_draw_rect(pSBElement, drgui_make_rect(0, thumbRect.bottom, drgui_get_width(pSBElement), drgui_get_height(pSBElement)), pSB->trackColor, pPaintData); // Bottom
|
|
drgui_draw_rect(pSBElement, drgui_make_rect(0, thumbRect.top, thumbRect.left, thumbRect.bottom), pSB->trackColor, pPaintData); // Left
|
|
drgui_draw_rect(pSBElement, drgui_make_rect(thumbRect.right, thumbRect.top, drgui_get_width(pSBElement), thumbRect.bottom), pSB->trackColor, pPaintData); // Right
|
|
|
|
// Thumb.
|
|
drgui_color thumbColor;
|
|
if (pSB->thumbPressed) {
|
|
thumbColor = pSB->thumbColorPressed;
|
|
} else if (pSB->thumbHovered) {
|
|
thumbColor = pSB->thumbColorHovered;
|
|
} else {
|
|
thumbColor = pSB->thumbColor;
|
|
}
|
|
|
|
drgui_draw_rect(pSBElement, thumbRect, thumbColor, pPaintData);
|
|
}
|
|
else
|
|
{
|
|
// The thumb is not visible - just draw the track as one quad.
|
|
drgui_draw_rect(pSBElement, drgui_get_local_rect(pSBElement), pSB->trackColor, pPaintData);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
DRGUI_PRIVATE void drgui_sb_refresh_thumb(drgui_element* pSBElement)
|
|
{
|
|
drgui_scrollbar* pSB = (drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
assert(pSB != NULL);
|
|
|
|
drgui_rect oldThumbRect = drgui_sb_get_thumb_rect(pSBElement);
|
|
|
|
pSB->thumbSize = drgui_sb_calculate_thumb_size(pSBElement);
|
|
pSB->thumbPos = drgui_sb_calculate_thumb_position(pSBElement);
|
|
|
|
drgui_rect newThumbRect = drgui_sb_get_thumb_rect(pSBElement);
|
|
if (!drgui_rect_equal(oldThumbRect, newThumbRect))
|
|
{
|
|
drgui_dirty(pSBElement, drgui_rect_union(oldThumbRect, newThumbRect));
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE float drgui_sb_calculate_thumb_size(drgui_element* pSBElement)
|
|
{
|
|
const drgui_scrollbar* pSB = (const drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
assert(pSB != NULL);
|
|
|
|
float trackSize = drgui_sb_get_track_size(pSBElement);
|
|
float range = (float)(pSB->rangeMax - pSB->rangeMin + 1);
|
|
|
|
float thumbSize = DRGUI_MIN_SCROLLBAR_THUMB_SIZE;
|
|
if (range > 0)
|
|
{
|
|
thumbSize = roundf((trackSize / range) * pSB->pageSize);
|
|
thumbSize = drgui_sb_clampf(thumbSize, DRGUI_MIN_SCROLLBAR_THUMB_SIZE, trackSize);
|
|
}
|
|
|
|
return thumbSize;
|
|
}
|
|
|
|
DRGUI_PRIVATE float drgui_sb_calculate_thumb_position(drgui_element* pSBElement)
|
|
{
|
|
const drgui_scrollbar* pSB = (const drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
assert(pSB != NULL);
|
|
|
|
float trackSize = drgui_sb_get_track_size(pSBElement);
|
|
float thumbSize = drgui_sb_calculate_thumb_size(pSBElement);
|
|
float range = (float)(pSB->rangeMax - pSB->rangeMin + 1);
|
|
|
|
float thumbPos = 0;
|
|
if (range > pSB->pageSize)
|
|
{
|
|
thumbPos = roundf((trackSize / range) * pSB->scrollPos);
|
|
thumbPos = drgui_sb_clampf(thumbPos, 0, trackSize - thumbSize);
|
|
}
|
|
|
|
return thumbPos;
|
|
}
|
|
|
|
DRGUI_PRIVATE float drgui_sb_get_track_size(drgui_element* pSBElement)
|
|
{
|
|
const drgui_scrollbar* pSB = (const drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
assert(pSB != NULL);
|
|
|
|
if (pSB->orientation == drgui_sb_orientation_vertical) {
|
|
return drgui_get_height(pSBElement) - (pSB->thumbPadding*2);
|
|
} else {
|
|
return drgui_get_width(pSBElement) - (pSB->thumbPadding*2);
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_sb_make_relative_to_thumb(drgui_element* pSBElement, float* pPosX, float* pPosY)
|
|
{
|
|
drgui_rect thumbRect = drgui_sb_get_thumb_rect(pSBElement);
|
|
|
|
if (pPosX != NULL) {
|
|
*pPosX -= thumbRect.left;
|
|
}
|
|
|
|
if (pPosY != NULL) {
|
|
*pPosY -= thumbRect.top;
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE int drgui_sb_calculate_scroll_pos_from_thumb_pos(drgui_element* pSBElement, float thumbPos)
|
|
{
|
|
const drgui_scrollbar* pSB = (const drgui_scrollbar*)drgui_get_extra_data(pSBElement);
|
|
assert(pSB != NULL);
|
|
|
|
float trackSize = drgui_sb_get_track_size(pSBElement);
|
|
float range = (float)(pSB->rangeMax - pSB->rangeMin + 1);
|
|
|
|
return (int)roundf(thumbPos / (trackSize / range));
|
|
}
|
|
#endif //DR_GUI_IMPLEMENTATION
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Tab Bar
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// QUICK NOTES
|
|
//
|
|
// - This control is only the tab bar itself - this does not handle tab pages and content switching and whatnot.
|
|
|
|
#ifndef drgui_tab_bar_h
|
|
#define drgui_tab_bar_h
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define DRGUI_MAX_TAB_TEXT_LENGTH 256
|
|
|
|
typedef enum
|
|
{
|
|
drgui_tabbar_orientation_top,
|
|
drgui_tabbar_orientation_bottom,
|
|
drgui_tabbar_orientation_left,
|
|
drgui_tabbar_orientation_right
|
|
} drgui_tabbar_orientation;
|
|
|
|
typedef struct drgui_tab drgui_tab;
|
|
|
|
typedef void (* drgui_tabbar_on_measure_tab_proc) (drgui_element* pTBElement, drgui_tab* pTab, float* pWidthOut, float* pHeightOut);
|
|
typedef void (* drgui_tabbar_on_paint_tab_proc) (drgui_element* pTBElement, drgui_tab* pTab, drgui_rect relativeClippingRect, float offsetX, float offsetY, float width, float height, void* pPaintData);
|
|
typedef void (* drgui_tabbar_on_tab_activated_proc) (drgui_element* pTBElement, drgui_tab* pTab, drgui_tab* pOldActiveTab);
|
|
typedef void (* drgui_tabbar_on_tab_deactivated_proc) (drgui_element* pTBElement, drgui_tab* pTab, drgui_tab* pNewActiveTab);
|
|
typedef void (* drgui_tabbar_on_tab_close_proc) (drgui_element* pTBElement, drgui_tab* pTab);
|
|
typedef void (* drgui_tabbar_on_tab_mouse_button_up_proc)(drgui_element* pTBElement, drgui_tab* pTab, int mouseButton, int mouseRelativePosX, int mouseRelativePosY, int stateFlags);
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Tab Bar
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Creates a new tab bar control.
|
|
drgui_element* drgui_create_tab_bar(drgui_context* pContext, drgui_element* pParent, drgui_tabbar_orientation orientation, size_t extraDataSize, const void* pExtraData);
|
|
|
|
/// Deletes the given tab bar control.
|
|
void drgui_delete_tab_bar(drgui_element* pTBElement);
|
|
|
|
|
|
/// Retrieves the size of the extra data associated with the scrollbar.
|
|
size_t drgui_tabbar_get_extra_data_size(drgui_element* pTBElement);
|
|
|
|
/// Retrieves a pointer to the extra data associated with the scrollbar.
|
|
void* drgui_tabbar_get_extra_data(drgui_element* pTBElement);
|
|
|
|
/// Retrieves the orientation of the given scrollbar.
|
|
drgui_tabbar_orientation drgui_tabbar_get_orientation(drgui_element* pTBElement);
|
|
|
|
|
|
/// Sets the default font to use for tabs.
|
|
void drgui_tabbar_set_font(drgui_element* pTBElement, drgui_font* pFont);
|
|
|
|
/// Retrieves the default font to use for tabs.
|
|
drgui_font* drgui_tabbar_get_font(drgui_element* pTBElement);
|
|
|
|
// Sets the color of the text to use on tabs.
|
|
void drgui_tabbar_set_text_color(drgui_element* pTBElement, drgui_color color);
|
|
|
|
// Retrieves the color of the text to use on tabs.
|
|
drgui_color drgui_tabbar_get_text_color(drgui_element* pTBElement);
|
|
|
|
// Sets the color of the text to use on active tabs.
|
|
void drgui_tabbar_set_text_color_active(drgui_element* pTBElement, drgui_color color);
|
|
|
|
// Sets the color of the text to use on hovered tabs.
|
|
void drgui_tabbar_set_text_color_hovered(drgui_element* pTBElement, drgui_color color);
|
|
|
|
/// Sets the image to use for close buttons.
|
|
void drgui_tabbar_set_close_button_image(drgui_element* pTBElement, drgui_image* pImage);
|
|
|
|
/// Retrieves the image being used for the close buttons.
|
|
drgui_image* drgui_tabbar_get_close_button_image(drgui_element* pTBElement);
|
|
|
|
// Sets the default color of the close button.
|
|
void drgui_tabbar_set_close_button_color(drgui_element* pTBElement, drgui_color color);
|
|
|
|
// Sets the padding to apply the the text of each tab.
|
|
void drgui_tabbar_set_tab_padding(drgui_element* pTBElement, float padding);
|
|
|
|
// Retrieves the padding to apply to the text of each tab.
|
|
float drgui_tabbar_get_tab_padding(drgui_element* pTBElement);
|
|
|
|
// Sets the padding to apply the the left of the close button.
|
|
void drgui_tabbar_set_close_button_left_padding(drgui_element* pTBElement, float padding);
|
|
|
|
// Retrieves the padding to apply to the left of the close button.
|
|
float drgui_tabbar_get_close_button_left_padding(drgui_element* pTBElement);
|
|
|
|
// Sets the default background color of tabs. This is the color of inactive tabs.
|
|
void drgui_tabbar_set_tab_background_color(drgui_element* pTBElement, drgui_color color);
|
|
|
|
// Retrieves the default background color of tabs while inactive.
|
|
drgui_color drgui_tabbar_get_tab_background_color(drgui_element* pTBElement);
|
|
|
|
// Sets the background color of tabs while hovered.
|
|
void drgui_tabbar_set_tab_background_color_hovered(drgui_element* pTBElement, drgui_color color);
|
|
|
|
// Retrieves the background color of tabs while hovered.
|
|
drgui_color drgui_tabbar_get_tab_background_color_hovered(drgui_element* pTBElement);
|
|
|
|
// Sets the background color of tabs while activated.
|
|
void drgui_tabbar_set_tab_background_color_active(drgui_element* pTBElement, drgui_color color);
|
|
|
|
// Retrieves the background color of tabs while activated.
|
|
drgui_color drgui_tabbar_get_tab_background_color_actived(drgui_element* pTBElement);
|
|
|
|
|
|
/// Sets the function to call when a tab needs to be measured.
|
|
void drgui_tabbar_set_on_measure_tab(drgui_element* pTBElement, drgui_tabbar_on_measure_tab_proc proc);
|
|
|
|
/// Sets the function to call when a tab needs to be painted.
|
|
void drgui_tabbar_set_on_paint_tab(drgui_element* pTBElement, drgui_tabbar_on_paint_tab_proc proc);
|
|
|
|
/// Sets the function to call when a tab is activated.
|
|
void drgui_tabbar_set_on_tab_activated(drgui_element* pTBElement, drgui_tabbar_on_tab_activated_proc proc);
|
|
|
|
/// Sets the function to call when a tab is deactivated.
|
|
void drgui_tabbar_set_on_tab_deactivated(drgui_element* pTBElement, drgui_tabbar_on_tab_deactivated_proc proc);
|
|
|
|
/// Sets the function to call when a tab is closed with the close button.
|
|
void drgui_tabbar_set_on_tab_closed(drgui_element* pTBElement, drgui_tabbar_on_tab_close_proc proc);
|
|
|
|
// Sets the function to call when a tab has a mouse button released on it.
|
|
void drgui_tabbar_set_on_tab_mouse_button_up(drgui_element* pTBElement, drgui_tabbar_on_tab_mouse_button_up_proc proc);
|
|
|
|
|
|
/// Measures the given tab.
|
|
void drgui_tabbar_measure_tab(drgui_element* pTBElement, drgui_tab* pTab, float* pWidthOut, float* pHeightOut);
|
|
|
|
/// Paints the given tab.
|
|
void drgui_tabbar_paint_tab(drgui_element* pTBElement, drgui_tab* pTab, drgui_rect relativeClippingRect, float offsetX, float offsetY, float width, float height, void* pPaintData);
|
|
|
|
|
|
/// Sets the width or height of the tab bar to that of it's tabs based on it's orientation.
|
|
///
|
|
/// @remarks
|
|
/// If the orientation is set to top or bottom, the height will be resized and the width will be left alone. If the orientation
|
|
/// is left or right, the width will be resized and the height will be left alone.
|
|
/// @par
|
|
/// If there is no tab measuring callback set, this will do nothing.
|
|
void drgui_tabbar_resize_by_tabs(drgui_element* pTBElement);
|
|
|
|
/// Enables auto-resizing based on tabs.
|
|
///
|
|
/// @remarks
|
|
/// This follows the same resizing rules as per drgui_tabbar_resize_by_tabs().
|
|
///
|
|
/// @see
|
|
/// drgui_tabbar_resize_by_tabs()
|
|
void drgui_tabbar_enable_auto_size(drgui_element* pTBElement);
|
|
|
|
/// Disables auto-resizing based on tabs.
|
|
void drgui_tabbar_disable_auto_size(drgui_element* pTBElement);
|
|
|
|
/// Determines whether or not auto-sizing is enabled.
|
|
bool drgui_tabbar_is_auto_size_enabled(drgui_element* pTBElement);
|
|
|
|
|
|
// Retrieves a pointer to the first tab in the given tab bar.
|
|
drgui_tab* drgui_tabbar_get_first_tab(drgui_element* pTBElement);
|
|
|
|
// Retrieves a pointer to the last tab in the given tab bar.
|
|
drgui_tab* drgui_tabbar_get_last_tab(drgui_element* pTBElement);
|
|
|
|
// Retrieves a pointer to the next tab in the given tab bar.
|
|
drgui_tab* drgui_tabbar_get_next_tab(drgui_element* pTBElement, drgui_tab* pTab);
|
|
|
|
// Retrieves a pointer to the previous tab in the given tab bar.
|
|
drgui_tab* drgui_tabbar_get_prev_tab(drgui_element* pTBElement, drgui_tab* pTab);
|
|
|
|
|
|
/// Activates the given tab.
|
|
void drgui_tabbar_activate_tab(drgui_element* pTBElement, drgui_tab* pTab);
|
|
|
|
// Activates the tab to the right of the currently active tab, looping back to the start if necessary.
|
|
void drgui_tabbar_activate_next_tab(drgui_element* pTBElement);
|
|
|
|
// Activates the tab to the left of the currently active tab, looping back to the end if necessary.
|
|
void drgui_tabbar_activate_prev_tab(drgui_element* pTBElement);
|
|
|
|
/// Retrieves a pointer to the currently active tab.
|
|
drgui_tab* drgui_tabbar_get_active_tab(drgui_element* pTBElement);
|
|
|
|
|
|
/// Determines whether or not the given tab is in view.
|
|
bool drgui_tabbar_is_tab_in_view(drgui_element* pTBElement, drgui_tab* pTab);
|
|
|
|
|
|
/// Shows the close buttons on each tab.
|
|
void drgui_tabbar_show_close_buttons(drgui_element* pTBElement);
|
|
|
|
/// Hides the close buttons on each tab.
|
|
void drgui_tabbar_hide_close_buttons(drgui_element* pTBElement);
|
|
|
|
/// Enables the on_close event on middle click.
|
|
void drgui_tabbar_enable_close_on_middle_click(drgui_element* pTBElement);
|
|
|
|
/// Disables the on_close event on middle click.
|
|
void drgui_tabbar_disable_close_on_middle_click(drgui_element* pTBElement);
|
|
|
|
/// Determines whether or not close-on-middle-click is enabled.
|
|
bool drgui_tabbar_is_close_on_middle_click_enabled(drgui_element* pTBElement);
|
|
|
|
|
|
/// Called when the mouse leave event needs to be processed for the given tab bar control.
|
|
void drgui_tabbar_on_mouse_leave(drgui_element* pTBElement);
|
|
|
|
/// Called when the mouse move event needs to be processed for the given tab bar control.
|
|
void drgui_tabbar_on_mouse_move(drgui_element* pTBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the mouse button down event needs to be processed for the given tab bar control.
|
|
void drgui_tabbar_on_mouse_button_down(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the mouse button up event needs to be processed for the given tab bar control.
|
|
void drgui_tabbar_on_mouse_button_up(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the paint event needs to be processed for the given tab control.
|
|
void drgui_tabbar_on_paint(drgui_element* pTBElement, drgui_rect relativeClippingRect, void* pPaintData);
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Tab
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Creates and appends a tab
|
|
drgui_tab* drgui_tabbar_create_and_append_tab(drgui_element* pTBElement, const char* text, size_t extraDataSize, const void* pExtraData);
|
|
|
|
/// Creates and prepends a tab.
|
|
drgui_tab* drgui_tabbar_create_and_prepend_tab(drgui_element* pTBElement, const char* text, size_t extraDataSize, const void* pExtraData);
|
|
|
|
/// Recursively deletes a tree view item.
|
|
void drgui_tab_delete(drgui_tab* pTab);
|
|
|
|
/// Retrieves the tab bar GUI element that owns the given item.
|
|
drgui_element* drgui_tab_get_tab_bar_element(drgui_tab* pTab);
|
|
|
|
/// Retrieves the size of the extra data associated with the given tree-view item.
|
|
size_t drgui_tab_get_extra_data_size(drgui_tab* pTab);
|
|
|
|
/// Retrieves a pointer to the extra data associated with the given tree-view item.
|
|
void* drgui_tab_get_extra_data(drgui_tab* pTab);
|
|
|
|
|
|
/// Sets the text of the given tab bar item.
|
|
void drgui_tab_set_text(drgui_tab* pTab, const char* text);
|
|
|
|
/// Retrieves the text of the given tab bar item.
|
|
const char* drgui_tab_get_text(drgui_tab* pTab);
|
|
|
|
|
|
/// Retrieves a pointer to the next tab in the tab bar.
|
|
drgui_tab* drgui_tab_get_next_tab(drgui_tab* pTab);
|
|
|
|
/// Retrieves a pointer to the previous tab in the tab bar.
|
|
drgui_tab* drgui_tab_get_prev_tab(drgui_tab* pTab);
|
|
|
|
|
|
/// Moves the given tab to the front of the tab bar that owns it.
|
|
void drgui_tab_move_to_front(drgui_tab* pTab);
|
|
|
|
/// Determines whether or not the given tab is in view.
|
|
bool drgui_tab_is_in_view(drgui_tab* pTab);
|
|
|
|
/// Moves the given tab into view, if it's not already.
|
|
///
|
|
/// If the tab is out of view, it will be repositioned to the front of the tab bar.
|
|
void drgui_tab_move_into_view(drgui_tab* pTab);
|
|
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif //drgui_tab_bar_h
|
|
|
|
|
|
#ifdef DR_GUI_IMPLEMENTATION
|
|
typedef struct drgui_tab_bar drgui_tab_bar;
|
|
|
|
struct drgui_tab_bar
|
|
{
|
|
/// The orientation.
|
|
drgui_tabbar_orientation orientation;
|
|
|
|
|
|
/// A pointer to the first tab.
|
|
drgui_tab* pFirstTab;
|
|
|
|
/// A pointer to the last tab.
|
|
drgui_tab* pLastTab;
|
|
|
|
|
|
/// A pointer to the hovered tab.
|
|
drgui_tab* pHoveredTab;
|
|
|
|
/// A pointer to the active tab.
|
|
drgui_tab* pActiveTab;
|
|
|
|
/// The tab whose close button is currently pressed, if any.
|
|
drgui_tab* pTabWithCloseButtonPressed;
|
|
|
|
|
|
/// The default font to use for tab bar items.
|
|
drgui_font* pFont;
|
|
|
|
/// The default color to use for tab bar item text.
|
|
drgui_color tabTextColor;
|
|
|
|
/// The default color to use for tab bar item text while active.
|
|
drgui_color tabTextColorActivated;
|
|
|
|
/// The default color to use for tab bar item text while hovered.
|
|
drgui_color tabTextColorHovered;
|
|
|
|
/// The default background color of tab bar items.
|
|
drgui_color tabBackgroundColor;
|
|
|
|
/// The background color of tab bar items while hovered.
|
|
drgui_color tabBackgroundColorHovered;
|
|
|
|
/// The background color of tab bar items while selected.
|
|
drgui_color tabBackbroundColorActivated;
|
|
|
|
/// The padding to apply to the text of tabs.
|
|
float tabPadding;
|
|
|
|
/// The image to use for the close button.
|
|
drgui_image* pCloseButtonImage;
|
|
|
|
/// The padding to the left of the close button.
|
|
float closeButtonPaddingLeft;
|
|
|
|
/// The default color of the close button.
|
|
drgui_color closeButtonColorDefault;
|
|
|
|
/// The color of the close button when the tab is hovered, but not the close button itself.
|
|
drgui_color closeButtonColorTabHovered;
|
|
|
|
/// The color of the close button when it is hovered.
|
|
drgui_color closeButtonColorHovered;
|
|
|
|
/// The color of the close button when it is pressed.
|
|
drgui_color closeButtonColorPressed;
|
|
|
|
|
|
/// Whether or not auto-sizing is enabled. Disabled by default.
|
|
bool isAutoSizeEnabled;
|
|
|
|
/// Whether or not the close buttons are being shown.
|
|
bool isShowingCloseButton;
|
|
|
|
/// Whether or not close-on-middle-click is enabled.
|
|
bool isCloseOnMiddleClickEnabled;
|
|
|
|
/// Whether or not the close button is hovered.
|
|
bool isCloseButtonHovered;
|
|
|
|
|
|
/// The function to call when a tab needs to be measured.
|
|
drgui_tabbar_on_measure_tab_proc onMeasureTab;
|
|
|
|
/// The function to call when a tab needs to be painted.
|
|
drgui_tabbar_on_paint_tab_proc onPaintTab;
|
|
|
|
/// The function to call when a tab is activated.
|
|
drgui_tabbar_on_tab_activated_proc onTabActivated;
|
|
|
|
/// The function to call when a tab is deactivated.
|
|
drgui_tabbar_on_tab_deactivated_proc onTabDeactivated;
|
|
|
|
/// The function to call when a tab is closed via the close button.
|
|
drgui_tabbar_on_tab_close_proc onTabClose;
|
|
|
|
// The function to call when a mouse button is released while over a tab.
|
|
drgui_tabbar_on_tab_mouse_button_up_proc onTabMouseButtonUp;
|
|
|
|
|
|
/// The size of the extra data.
|
|
size_t extraDataSize;
|
|
|
|
/// A pointer to the extra data.
|
|
char pExtraData[1];
|
|
};
|
|
|
|
struct drgui_tab
|
|
{
|
|
/// The tab bar that owns the tab.
|
|
drgui_element* pTBElement;
|
|
|
|
/// A pointer to the next tab in the tab bar.
|
|
drgui_tab* pNextTab;
|
|
|
|
/// A pointer to the previous tab in the tab bar.
|
|
drgui_tab* pPrevTab;
|
|
|
|
|
|
/// The tab bar's text.
|
|
char text[DRGUI_MAX_TAB_TEXT_LENGTH];
|
|
|
|
|
|
/// The size of the extra data.
|
|
size_t extraDataSize;
|
|
|
|
/// A pointer to the extra data buffer.
|
|
char pExtraData[1];
|
|
};
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Tab
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Default implementation of the item measure event.
|
|
DRGUI_PRIVATE void drgui_tabbar_on_measure_tab_default(drgui_element* pTBElement, drgui_tab* pTab, float* pWidthOut, float* pHeightOut);
|
|
|
|
/// Paints the given menu item.
|
|
DRGUI_PRIVATE void drgui_tabbar_on_paint_tab_default(drgui_element* pTBElement, drgui_tab* pTab, drgui_rect relativeClippingRect, float offsetX, float offsetY, float width, float height, void* pPaintData);
|
|
|
|
/// Finds the tab sitting under the given point, if any.
|
|
DRGUI_PRIVATE drgui_tab* drgui_tabbar_find_tab_under_point(drgui_element* pTBElement, float relativePosX, float relativePosY, bool* pIsOverCloseButtonOut);
|
|
|
|
drgui_element* drgui_create_tab_bar(drgui_context* pContext, drgui_element* pParent, drgui_tabbar_orientation orientation, size_t extraDataSize, const void* pExtraData)
|
|
{
|
|
if (pContext == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_element* pTBElement = drgui_create_element(pContext, pParent, sizeof(drgui_tab_bar) + extraDataSize, NULL);
|
|
if (pTBElement == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
pTB->orientation = orientation;
|
|
pTB->pFirstTab = NULL;
|
|
pTB->pLastTab = NULL;
|
|
pTB->pHoveredTab = NULL;
|
|
pTB->pActiveTab = NULL;
|
|
pTB->pTabWithCloseButtonPressed = NULL;
|
|
|
|
pTB->pFont = NULL;
|
|
pTB->tabTextColor = drgui_rgb(224, 224, 224);
|
|
pTB->tabTextColorActivated = drgui_rgb(224, 224, 224);
|
|
pTB->tabTextColorHovered = drgui_rgb(224, 224, 224);
|
|
pTB->tabBackgroundColor = drgui_rgb(58, 58, 58);
|
|
pTB->tabBackgroundColorHovered = drgui_rgb(16, 92, 160);
|
|
pTB->tabBackbroundColorActivated = drgui_rgb(32, 128, 192); //drgui_rgb(80, 80, 80);
|
|
pTB->tabPadding = 4;
|
|
pTB->pCloseButtonImage = NULL;
|
|
pTB->closeButtonPaddingLeft = 6;
|
|
pTB->closeButtonColorDefault = pTB->tabBackgroundColor;
|
|
pTB->closeButtonColorTabHovered = drgui_rgb(192, 192, 192);
|
|
pTB->closeButtonColorHovered = drgui_rgb(255, 96, 96);
|
|
pTB->closeButtonColorPressed = drgui_rgb(192, 32, 32);
|
|
pTB->isAutoSizeEnabled = false;
|
|
pTB->isShowingCloseButton = false;
|
|
pTB->isCloseOnMiddleClickEnabled = false;
|
|
pTB->isCloseButtonHovered = false;
|
|
|
|
pTB->onMeasureTab = drgui_tabbar_on_measure_tab_default;
|
|
pTB->onPaintTab = drgui_tabbar_on_paint_tab_default;
|
|
pTB->onTabActivated = NULL;
|
|
pTB->onTabDeactivated = NULL;
|
|
pTB->onTabClose = NULL;
|
|
|
|
|
|
pTB->extraDataSize = extraDataSize;
|
|
if (pExtraData != NULL) {
|
|
memcpy(pTB->pExtraData, pExtraData, extraDataSize);
|
|
}
|
|
|
|
|
|
// Event handlers.
|
|
drgui_set_on_mouse_leave(pTBElement, drgui_tabbar_on_mouse_leave);
|
|
drgui_set_on_mouse_move(pTBElement, drgui_tabbar_on_mouse_move);
|
|
drgui_set_on_mouse_button_down(pTBElement, drgui_tabbar_on_mouse_button_down);
|
|
drgui_set_on_mouse_button_up(pTBElement, drgui_tabbar_on_mouse_button_up);
|
|
drgui_set_on_paint(pTBElement, drgui_tabbar_on_paint);
|
|
|
|
return pTBElement;
|
|
}
|
|
|
|
void drgui_delete_tab_bar(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
while (pTB->pFirstTab != NULL) {
|
|
drgui_tab_delete(pTB->pFirstTab);
|
|
}
|
|
|
|
|
|
drgui_delete_element(pTBElement);
|
|
}
|
|
|
|
|
|
size_t drgui_tabbar_get_extra_data_size(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTB->extraDataSize;
|
|
}
|
|
|
|
void* drgui_tabbar_get_extra_data(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTB->pExtraData;
|
|
}
|
|
|
|
drgui_tabbar_orientation drgui_tabbar_get_orientation(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return drgui_tabbar_orientation_top;
|
|
}
|
|
|
|
return pTB->orientation;
|
|
}
|
|
|
|
|
|
void drgui_tabbar_set_font(drgui_element* pTBElement, drgui_font* pFont)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->pFont = pFont;
|
|
|
|
// A change in font may have changed the size of the tabbar.
|
|
if (pTB->isAutoSizeEnabled) {
|
|
drgui_tabbar_resize_by_tabs(pTBElement);
|
|
}
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
drgui_font* drgui_tabbar_get_font(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTB->pFont;
|
|
}
|
|
|
|
|
|
void drgui_tabbar_set_text_color(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->tabTextColor = color;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
drgui_color drgui_tabbar_get_text_color(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTB->tabTextColor;
|
|
}
|
|
|
|
void drgui_tabbar_set_text_color_active(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->tabTextColorActivated = color;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
void drgui_tabbar_set_text_color_hovered(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->tabTextColorHovered = color;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_tabbar_set_close_button_image(drgui_element* pTBElement, drgui_image* pImage)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->pCloseButtonImage = pImage;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
drgui_image* drgui_tabbar_get_close_button_image(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTB->pCloseButtonImage;
|
|
}
|
|
|
|
void drgui_tabbar_set_close_button_color(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->closeButtonColorDefault = color;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_tabbar_set_tab_padding(drgui_element* pTBElement, float padding)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->tabPadding = padding;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
float drgui_tabbar_get_tab_padding(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTB->tabPadding;
|
|
}
|
|
|
|
void drgui_tabbar_set_close_button_left_padding(drgui_element* pTBElement, float padding)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->closeButtonPaddingLeft = padding;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
float drgui_tabbar_get_close_button_left_padding(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTB->closeButtonPaddingLeft;
|
|
}
|
|
|
|
|
|
void drgui_tabbar_set_tab_background_color(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->tabBackgroundColor = color;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
drgui_color drgui_tabbar_get_tab_background_color(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTB->tabBackgroundColor;
|
|
}
|
|
|
|
void drgui_tabbar_set_tab_background_color_hovered(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->tabBackgroundColorHovered = color;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
drgui_color drgui_tabbar_get_tab_background_color_hovered(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTB->tabBackgroundColorHovered;
|
|
}
|
|
|
|
void drgui_tabbar_set_tab_background_color_active(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->tabBackbroundColorActivated = color;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
drgui_color drgui_tabbar_get_tab_background_color_actived(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTB->tabBackbroundColorActivated;
|
|
}
|
|
|
|
|
|
void drgui_tabbar_set_on_measure_tab(drgui_element* pTBElement, drgui_tabbar_on_measure_tab_proc proc)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->onMeasureTab = proc;
|
|
}
|
|
|
|
void drgui_tabbar_set_on_paint_tab(drgui_element* pTBElement, drgui_tabbar_on_paint_tab_proc proc)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->onPaintTab = proc;
|
|
}
|
|
|
|
void drgui_tabbar_set_on_tab_activated(drgui_element* pTBElement, drgui_tabbar_on_tab_activated_proc proc)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->onTabActivated = proc;
|
|
}
|
|
|
|
void drgui_tabbar_set_on_tab_deactivated(drgui_element* pTBElement, drgui_tabbar_on_tab_deactivated_proc proc)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->onTabDeactivated = proc;
|
|
}
|
|
|
|
void drgui_tabbar_set_on_tab_closed(drgui_element* pTBElement, drgui_tabbar_on_tab_close_proc proc)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->onTabClose = proc;
|
|
}
|
|
|
|
void drgui_tabbar_set_on_tab_mouse_button_up(drgui_element* pTBElement, drgui_tabbar_on_tab_mouse_button_up_proc proc)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->onTabMouseButtonUp = proc;
|
|
}
|
|
|
|
|
|
void drgui_tabbar_measure_tab(drgui_element* pTBElement, drgui_tab* pTab, float* pWidthOut, float* pHeightOut)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTB->onMeasureTab) {
|
|
pTB->onMeasureTab(pTBElement, pTab, pWidthOut, pHeightOut);
|
|
}
|
|
}
|
|
|
|
void drgui_tabbar_paint_tab(drgui_element* pTBElement, drgui_tab* pTab, drgui_rect relativeClippingRect, float offsetX, float offsetY, float width, float height, void* pPaintData)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTB->onPaintTab) {
|
|
pTB->onPaintTab(pTBElement, pTab, relativeClippingRect, offsetX, offsetY, width, height, pPaintData);
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_tabbar_resize_by_tabs(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTB->onMeasureTab == NULL) {
|
|
return;
|
|
}
|
|
|
|
float maxWidth = 0;
|
|
float maxHeight = 0;
|
|
if (pTB->pFirstTab == NULL) {
|
|
// There are no tabs. Set initial size based on the line height of the font.
|
|
drgui_font_metrics fontMetrics;
|
|
if (drgui_get_font_metrics(pTB->pFont, &fontMetrics)) {
|
|
if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) {
|
|
maxHeight = fontMetrics.lineHeight + (pTB->tabPadding*2);
|
|
} else {
|
|
maxWidth = fontMetrics.lineHeight + (pTB->tabPadding*2);
|
|
}
|
|
}
|
|
} else {
|
|
for (drgui_tab* pTab = pTB->pFirstTab; pTab != NULL; pTab = pTab->pNextTab) {
|
|
float tabWidth = 0;
|
|
float tabHeight = 0;
|
|
drgui_tabbar_measure_tab(pTBElement, pTab, &tabWidth, &tabHeight);
|
|
|
|
maxWidth = (tabWidth > maxWidth) ? tabWidth : maxWidth;
|
|
maxHeight = (tabHeight > maxHeight) ? tabHeight : maxHeight;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) {
|
|
drgui_set_size(pTBElement, drgui_get_width(pTBElement), maxHeight);
|
|
} else {
|
|
drgui_set_size(pTBElement, maxWidth, drgui_get_height(pTBElement));
|
|
}
|
|
}
|
|
|
|
void drgui_tabbar_enable_auto_size(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->isAutoSizeEnabled = true;
|
|
}
|
|
|
|
void drgui_tabbar_disable_auto_size(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->isAutoSizeEnabled = false;
|
|
}
|
|
|
|
bool drgui_tabbar_is_auto_size_enabled(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pTB->isAutoSizeEnabled;
|
|
}
|
|
|
|
|
|
drgui_tab* drgui_tabbar_get_first_tab(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTB->pFirstTab;
|
|
}
|
|
|
|
drgui_tab* drgui_tabbar_get_last_tab(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTB->pLastTab;
|
|
}
|
|
|
|
drgui_tab* drgui_tabbar_get_next_tab(drgui_element* pTBElement, drgui_tab* pTab)
|
|
{
|
|
(void)pTBElement;
|
|
return drgui_tab_get_next_tab(pTab);
|
|
}
|
|
|
|
drgui_tab* drgui_tabbar_get_prev_tab(drgui_element* pTBElement, drgui_tab* pTab)
|
|
{
|
|
(void)pTBElement;
|
|
return drgui_tab_get_prev_tab(pTab);
|
|
}
|
|
|
|
|
|
void drgui_tabbar_activate_tab(drgui_element* pTBElement, drgui_tab* pTab)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tab* pOldActiveTab = pTB->pActiveTab;
|
|
drgui_tab* pNewActiveTab = pTab;
|
|
|
|
if (pOldActiveTab == pNewActiveTab) {
|
|
return; // The tab is already active - nothing to do.
|
|
}
|
|
|
|
|
|
pTB->pActiveTab = pNewActiveTab;
|
|
|
|
if (pTB->onTabDeactivated && pOldActiveTab != NULL) {
|
|
pTB->onTabDeactivated(pTBElement, pOldActiveTab, pNewActiveTab);
|
|
}
|
|
|
|
if (pTB->onTabActivated && pNewActiveTab != NULL) {
|
|
pTB->onTabActivated(pTBElement, pNewActiveTab, pOldActiveTab);
|
|
}
|
|
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
void drgui_tabbar_activate_next_tab(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTB->pActiveTab == NULL) {
|
|
drgui_tabbar_activate_tab(pTBElement, pTB->pFirstTab);
|
|
return;
|
|
}
|
|
|
|
|
|
drgui_tab* pNextTab = pTB->pActiveTab->pNextTab;
|
|
if (pNextTab == NULL) {
|
|
pNextTab = pTB->pFirstTab;
|
|
}
|
|
|
|
drgui_tabbar_activate_tab(pTBElement, pNextTab);
|
|
}
|
|
|
|
void drgui_tabbar_activate_prev_tab(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTB->pActiveTab == NULL) {
|
|
drgui_tabbar_activate_tab(pTBElement, pTB->pLastTab);
|
|
return;
|
|
}
|
|
|
|
|
|
drgui_tab* pPrevTab = pTB->pActiveTab->pPrevTab;
|
|
if (pPrevTab == NULL) {
|
|
pPrevTab = pTB->pLastTab;
|
|
}
|
|
|
|
drgui_tabbar_activate_tab(pTBElement, pPrevTab);
|
|
}
|
|
|
|
drgui_tab* drgui_tabbar_get_active_tab(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTB->pActiveTab;
|
|
}
|
|
|
|
|
|
bool drgui_tabbar_is_tab_in_view(drgui_element* pTBElement, drgui_tab* pTabIn)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
float tabbarWidth = 0;
|
|
float tabbarHeight = 0;
|
|
drgui_get_size(pTBElement, &tabbarWidth, &tabbarHeight);
|
|
|
|
|
|
// Each tab.
|
|
float runningPosX = 0;
|
|
float runningPosY = 0;
|
|
for (drgui_tab* pTab = pTB->pFirstTab; pTab != NULL; pTab = pTab->pNextTab)
|
|
{
|
|
float tabWidth = 0;
|
|
float tabHeight = 0;
|
|
drgui_tabbar_measure_tab(pTBElement, pTab, &tabWidth, &tabHeight);
|
|
|
|
if (pTab == pTabIn) {
|
|
return runningPosX + tabWidth <= tabbarWidth && runningPosY + tabHeight <= tabbarHeight;
|
|
}
|
|
|
|
|
|
if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) {
|
|
runningPosX += tabWidth;
|
|
} else {
|
|
runningPosY += tabHeight;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void drgui_tabbar_show_close_buttons(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->isShowingCloseButton = true;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
void drgui_tabbar_hide_close_buttons(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->isShowingCloseButton = false;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_tabbar_enable_close_on_middle_click(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->isCloseOnMiddleClickEnabled = true;
|
|
}
|
|
|
|
void drgui_tabbar_disable_close_on_middle_click(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->isCloseOnMiddleClickEnabled = false;
|
|
}
|
|
|
|
bool drgui_tabbar_is_close_on_middle_click_enabled(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pTB->isCloseOnMiddleClickEnabled;
|
|
}
|
|
|
|
|
|
void drgui_tabbar_on_mouse_leave(drgui_element* pTBElement)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTB->pHoveredTab != NULL)
|
|
{
|
|
pTB->pHoveredTab = NULL;
|
|
pTB->isCloseButtonHovered = false;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_tabbar_on_mouse_move(drgui_element* pTBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)stateFlags;
|
|
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
bool isCloseButtonHovered = false;
|
|
|
|
drgui_tab* pOldHoveredTab = pTB->pHoveredTab;
|
|
drgui_tab* pNewHoveredTab = drgui_tabbar_find_tab_under_point(pTBElement, (float)relativeMousePosX, (float)relativeMousePosY, &isCloseButtonHovered);
|
|
|
|
if (pOldHoveredTab != pNewHoveredTab || pTB->isCloseButtonHovered != isCloseButtonHovered)
|
|
{
|
|
pTB->pHoveredTab = pNewHoveredTab;
|
|
pTB->isCloseButtonHovered = isCloseButtonHovered;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_tabbar_on_mouse_button_down(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)stateFlags;
|
|
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT || mouseButton == DRGUI_MOUSE_BUTTON_RIGHT)
|
|
{
|
|
bool isOverCloseButton = false;
|
|
|
|
drgui_tab* pOldActiveTab = pTB->pActiveTab;
|
|
drgui_tab* pNewActiveTab = drgui_tabbar_find_tab_under_point(pTBElement, (float)relativeMousePosX, (float)relativeMousePosY, &isOverCloseButton);
|
|
|
|
if (pNewActiveTab != NULL && pOldActiveTab != pNewActiveTab && !isOverCloseButton) {
|
|
drgui_tabbar_activate_tab(pTBElement, pNewActiveTab);
|
|
}
|
|
|
|
if (isOverCloseButton && mouseButton == DRGUI_MOUSE_BUTTON_LEFT) {
|
|
pTB->pTabWithCloseButtonPressed = pNewActiveTab;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
}
|
|
else if (mouseButton == DRGUI_MOUSE_BUTTON_MIDDLE)
|
|
{
|
|
if (pTB->isCloseOnMiddleClickEnabled)
|
|
{
|
|
drgui_tab* pHoveredTab = drgui_tabbar_find_tab_under_point(pTBElement, (float)relativeMousePosX, (float)relativeMousePosY, NULL);
|
|
if (pHoveredTab != NULL) {
|
|
if (pTB->onTabClose) {
|
|
pTB->onTabClose(pTBElement, pHoveredTab);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_tabbar_on_mouse_button_up(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)stateFlags;
|
|
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
bool releasedOverCloseButton = false;
|
|
drgui_tab* pTabUnderMouse = drgui_tabbar_find_tab_under_point(pTBElement, (float)relativeMousePosX, (float)relativeMousePosY, &releasedOverCloseButton);
|
|
|
|
if (pTB->pTabWithCloseButtonPressed != NULL && mouseButton == DRGUI_MOUSE_BUTTON_LEFT)
|
|
{
|
|
if (releasedOverCloseButton && pTabUnderMouse == pTB->pTabWithCloseButtonPressed) {
|
|
if (pTB->onTabClose) {
|
|
pTB->onTabClose(pTBElement, pTB->pTabWithCloseButtonPressed);
|
|
}
|
|
}
|
|
|
|
|
|
pTB->pTabWithCloseButtonPressed = NULL;
|
|
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!releasedOverCloseButton && pTB->onTabMouseButtonUp) {
|
|
// TODO: Improve this by passing the mouse position relative to the tab. Currently it is relative to the tab BAR. Can have
|
|
// the drgui_tabbar_find_tab_under_point() function return the position relative to the tab.
|
|
pTB->onTabMouseButtonUp(pTBElement, pTabUnderMouse, mouseButton, relativeMousePosX, relativeMousePosY, stateFlags);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_tabbar_on_paint(drgui_element* pTBElement, drgui_rect relativeClippingRect, void* pPaintData)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
float tabbarWidth = 0;
|
|
float tabbarHeight = 0;
|
|
drgui_get_size(pTBElement, &tabbarWidth, &tabbarHeight);
|
|
|
|
|
|
// Each tab.
|
|
float runningPosX = 0;
|
|
float runningPosY = 0;
|
|
for (drgui_tab* pTab = pTB->pFirstTab; pTab != NULL; pTab = pTab->pNextTab)
|
|
{
|
|
float tabWidth = 0;
|
|
float tabHeight = 0;
|
|
drgui_tabbar_measure_tab(pTBElement, pTab, &tabWidth, &tabHeight);
|
|
|
|
// If a part of the tab is out of bounds, stop drawing.
|
|
if (runningPosX > tabbarWidth || runningPosY > tabbarHeight) {
|
|
break;
|
|
}
|
|
|
|
|
|
drgui_tabbar_paint_tab(pTBElement, pTab, relativeClippingRect, runningPosX, runningPosY, tabWidth, tabHeight, pPaintData);
|
|
|
|
// After painting the tab, there may be a region of the background that was not drawn by the tab painting callback. We'll need to
|
|
// draw that here.
|
|
if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) {
|
|
drgui_draw_rect(pTBElement, drgui_make_rect(runningPosX, runningPosY + tabHeight, tabbarWidth, tabbarHeight), pTB->tabBackgroundColor, pPaintData);
|
|
} else {
|
|
drgui_draw_rect(pTBElement, drgui_make_rect(runningPosX + tabWidth, runningPosY, tabbarWidth, runningPosY + tabHeight), pTB->tabBackgroundColor, pPaintData);
|
|
}
|
|
|
|
|
|
|
|
if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) {
|
|
runningPosX += tabWidth;
|
|
} else {
|
|
runningPosY += tabHeight;
|
|
}
|
|
}
|
|
|
|
|
|
// Background. We just draw a quad around the region that is not covered by items.
|
|
drgui_draw_rect(pTBElement, drgui_make_rect(runningPosX, runningPosY, tabbarWidth, tabbarHeight), pTB->tabBackgroundColor, pPaintData);
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE void drgui_tabbar_on_measure_tab_default(drgui_element* pTBElement, drgui_tab* pTab, float* pWidthOut, float* pHeightOut)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
float textWidth = 0;
|
|
float textHeight = 0;
|
|
|
|
if (pTab != NULL) {
|
|
drgui_measure_string(pTB->pFont, pTab->text, strlen(pTab->text), &textWidth, &textHeight);
|
|
}
|
|
|
|
|
|
float closeButtonWidth = 0;
|
|
if (pTB->isShowingCloseButton && pTB->pCloseButtonImage != NULL) {
|
|
unsigned int closeImageWidth;
|
|
drgui_get_image_size(pTB->pCloseButtonImage, &closeImageWidth, NULL);
|
|
|
|
closeButtonWidth = closeImageWidth + pTB->closeButtonPaddingLeft;
|
|
}
|
|
|
|
|
|
if (pWidthOut) {
|
|
*pWidthOut = textWidth + closeButtonWidth + pTB->tabPadding*2;
|
|
}
|
|
if (pHeightOut) {
|
|
*pHeightOut = textHeight + pTB->tabPadding*2;
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_tabbar_on_paint_tab_default(drgui_element* pTBElement, drgui_tab* pTab, drgui_rect relativeClippingRect, float offsetX, float offsetY, float width, float height, void* pPaintData)
|
|
{
|
|
(void)relativeClippingRect;
|
|
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Background.
|
|
drgui_color bgcolor = pTB->tabBackgroundColor;
|
|
drgui_color closeButtonColor = pTB->closeButtonColorDefault;
|
|
drgui_color textColor = pTB->tabTextColor;
|
|
|
|
if (pTB->pHoveredTab == pTab) {
|
|
bgcolor = pTB->tabBackgroundColorHovered;
|
|
closeButtonColor = pTB->closeButtonColorTabHovered;
|
|
textColor = pTB->tabTextColorHovered;
|
|
}
|
|
if (pTB->pActiveTab == pTab) {
|
|
bgcolor = pTB->tabBackbroundColorActivated;
|
|
closeButtonColor = pTB->closeButtonColorTabHovered;
|
|
textColor = pTB->tabTextColorActivated;
|
|
}
|
|
|
|
if (pTB->pHoveredTab == pTab && pTB->isCloseButtonHovered) {
|
|
closeButtonColor = pTB->closeButtonColorHovered;
|
|
|
|
if (pTB->pTabWithCloseButtonPressed == pTB->pHoveredTab) {
|
|
closeButtonColor = pTB->closeButtonColorPressed;
|
|
}
|
|
}
|
|
|
|
drgui_draw_rect_outline(pTBElement, drgui_make_rect(offsetX, offsetY, offsetX + width, offsetY + height), bgcolor, pTB->tabPadding, pPaintData);
|
|
|
|
|
|
// Text.
|
|
float textPosX = offsetX + pTB->tabPadding;
|
|
float textPosY = offsetY + pTB->tabPadding;
|
|
if (pTab != NULL) {
|
|
drgui_draw_text(pTBElement, pTB->pFont, pTab->text, (int)strlen(pTab->text), textPosX, textPosY, textColor, bgcolor, pPaintData);
|
|
}
|
|
|
|
|
|
// Close button.
|
|
if (pTB->isShowingCloseButton && pTB->pCloseButtonImage != NULL)
|
|
{
|
|
float textWidth = 0;
|
|
float textHeight = 0;
|
|
if (pTab != NULL) {
|
|
drgui_measure_string(pTB->pFont, pTab->text, strlen(pTab->text), &textWidth, &textHeight);
|
|
}
|
|
|
|
float closeButtonPosX = textPosX + textWidth + pTB->closeButtonPaddingLeft;
|
|
float closeButtonPosY = textPosY;
|
|
|
|
unsigned int iconWidth;
|
|
unsigned int iconHeight;
|
|
drgui_get_image_size(pTB->pCloseButtonImage, &iconWidth, &iconHeight);
|
|
|
|
drgui_draw_image_args args;
|
|
args.dstX = closeButtonPosX;
|
|
args.dstY = closeButtonPosY;
|
|
args.dstWidth = (float)iconWidth;
|
|
args.dstHeight = (float)iconHeight;
|
|
args.srcX = 0;
|
|
args.srcY = 0;
|
|
args.srcWidth = (float)iconWidth;
|
|
args.srcHeight = (float)iconHeight;
|
|
args.dstBoundsX = args.dstX;
|
|
args.dstBoundsY = args.dstY;
|
|
args.dstBoundsWidth = (float)iconWidth;
|
|
args.dstBoundsHeight = height - (pTB->tabPadding*2);
|
|
args.foregroundTint = closeButtonColor;
|
|
args.backgroundColor = bgcolor;
|
|
args.boundsColor = bgcolor;
|
|
args.options = DRGUI_IMAGE_DRAW_BACKGROUND | DRGUI_IMAGE_DRAW_BOUNDS | DRGUI_IMAGE_CLIP_BOUNDS | DRGUI_IMAGE_ALIGN_CENTER;
|
|
drgui_draw_image(pTBElement, pTB->pCloseButtonImage, &args, pPaintData);
|
|
|
|
|
|
/// Space between the text and the padding.
|
|
drgui_draw_rect(pTBElement, drgui_make_rect(textPosX + textWidth, textPosY, closeButtonPosX, textPosY + textHeight), bgcolor, pPaintData);
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE drgui_tab* drgui_tabbar_find_tab_under_point(drgui_element* pTBElement, float relativePosX, float relativePosY, bool* pIsOverCloseButtonOut)
|
|
{
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int closeButtonWidth;
|
|
unsigned int closeButtonHeight;
|
|
drgui_get_image_size(pTB->pCloseButtonImage, &closeButtonWidth, &closeButtonHeight);
|
|
|
|
float runningPosX = 0;
|
|
float runningPosY = 0;
|
|
for (drgui_tab* pTab = pTB->pFirstTab; pTab != NULL; pTab = pTab->pNextTab)
|
|
{
|
|
float tabWidth = 0;
|
|
float tabHeight = 0;
|
|
drgui_tabbar_measure_tab(pTBElement, pTab, &tabWidth, &tabHeight);
|
|
|
|
if (relativePosX >= runningPosX && relativePosX < runningPosX + tabWidth && relativePosY >= runningPosY && relativePosY < runningPosY + tabHeight)
|
|
{
|
|
if (pIsOverCloseButtonOut)
|
|
{
|
|
// The close button is in the center, vertically.
|
|
drgui_rect closeButtonRect;
|
|
closeButtonRect.left = runningPosX + tabWidth - (pTB->tabPadding + closeButtonWidth);
|
|
closeButtonRect.right = closeButtonRect.left + closeButtonWidth;
|
|
closeButtonRect.top = runningPosY + (tabHeight - (pTB->tabPadding + closeButtonHeight))/2;
|
|
closeButtonRect.bottom = closeButtonRect.top + closeButtonHeight;
|
|
|
|
if (pTB->isShowingCloseButton && drgui_rect_contains_point(closeButtonRect, relativePosX, relativePosY)) {
|
|
*pIsOverCloseButtonOut = true;
|
|
} else {
|
|
*pIsOverCloseButtonOut = false;
|
|
}
|
|
}
|
|
|
|
return pTab;
|
|
}
|
|
|
|
if (pTB->orientation == drgui_tabbar_orientation_top || pTB->orientation == drgui_tabbar_orientation_bottom) {
|
|
runningPosX += tabWidth;
|
|
} else {
|
|
runningPosY += tabHeight;
|
|
}
|
|
}
|
|
|
|
|
|
if (pIsOverCloseButtonOut) {
|
|
*pIsOverCloseButtonOut = false;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Tab
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Appends the given tab to the given tab bar.
|
|
DRGUI_PRIVATE void tab_append(drgui_tab* pTab, drgui_element* pTBElement);
|
|
|
|
/// Prepends the given tab to the given tab bar.
|
|
DRGUI_PRIVATE void tab_prepend(drgui_tab* pTab, drgui_element* pTBElement);
|
|
|
|
/// Detaches the given tab bar from it's tab bar element's hierarchy.
|
|
///
|
|
/// @remarks
|
|
/// This does not deactivate the tab or what - it only detaches the tab from the hierarchy.
|
|
DRGUI_PRIVATE void tab_detach_from_hierarchy(drgui_tab* pTab);
|
|
|
|
/// Detaches the given tab bar from it's tab bar element.
|
|
DRGUI_PRIVATE void tab_detach(drgui_tab* pTab);
|
|
|
|
DRGUI_PRIVATE drgui_tab* tb_create_tab(drgui_element* pTBElement, const char* text, size_t extraDataSize, const void* pExtraData)
|
|
{
|
|
if (pTBElement == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_tab* pTab = (drgui_tab*)malloc(sizeof(*pTab) + extraDataSize);
|
|
if (pTab == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
pTab->pTBElement = NULL;
|
|
pTab->pNextTab = NULL;
|
|
pTab->pPrevTab = NULL;
|
|
pTab->text[0] = '\0';
|
|
|
|
pTab->extraDataSize = extraDataSize;
|
|
if (pExtraData) {
|
|
memcpy(pTab->pExtraData, pExtraData, extraDataSize);
|
|
}
|
|
|
|
if (text != NULL) {
|
|
drgui__strncpy_s(pTab->text, sizeof(pTab->text), text, (size_t)-1); // -1 = _TRUNCATE
|
|
}
|
|
|
|
return pTab;
|
|
}
|
|
|
|
drgui_tab* drgui_tabbar_create_and_append_tab(drgui_element* pTBElement, const char* text, size_t extraDataSize, const void* pExtraData)
|
|
{
|
|
drgui_tab* pTab = (drgui_tab*)tb_create_tab(pTBElement, text, extraDataSize, pExtraData);
|
|
if (pTab != NULL)
|
|
{
|
|
tab_append(pTab, pTBElement);
|
|
}
|
|
|
|
return pTab;
|
|
}
|
|
|
|
drgui_tab* drgui_tabbar_create_and_prepend_tab(drgui_element* pTBElement, const char* text, size_t extraDataSize, const void* pExtraData)
|
|
{
|
|
drgui_tab* pTab = (drgui_tab*)tb_create_tab(pTBElement, text, extraDataSize, pExtraData);
|
|
if (pTab != NULL)
|
|
{
|
|
tab_prepend(pTab, pTBElement);
|
|
}
|
|
|
|
return pTab;
|
|
}
|
|
|
|
void drgui_tab_delete(drgui_tab* pTab)
|
|
{
|
|
if (pTab == NULL) {
|
|
return;
|
|
}
|
|
|
|
tab_detach(pTab);
|
|
free(pTab);
|
|
}
|
|
|
|
drgui_element* drgui_tab_get_tab_bar_element(drgui_tab* pTab)
|
|
{
|
|
if (pTab == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTab->pTBElement;
|
|
}
|
|
|
|
size_t drgui_tab_get_extra_data_size(drgui_tab* pTab)
|
|
{
|
|
if (pTab == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTab->extraDataSize;
|
|
}
|
|
|
|
void* drgui_tab_get_extra_data(drgui_tab* pTab)
|
|
{
|
|
if (pTab == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTab->pExtraData;
|
|
}
|
|
|
|
|
|
void drgui_tab_set_text(drgui_tab* pTab, const char* text)
|
|
{
|
|
if (pTab == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (text != NULL) {
|
|
drgui__strncpy_s(pTab->text, sizeof(pTab->text), text, (size_t)-1); // -1 = _TRUNCATE
|
|
} else {
|
|
pTab->text[0] = '\0';
|
|
}
|
|
|
|
// The content of the menu has changed so we'll need to schedule a redraw.
|
|
if (drgui_is_auto_dirty_enabled(pTab->pTBElement->pContext)) {
|
|
drgui_dirty(pTab->pTBElement, drgui_get_local_rect(pTab->pTBElement));
|
|
}
|
|
}
|
|
|
|
const char* drgui_tab_get_text(drgui_tab* pTab)
|
|
{
|
|
if (pTab == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTab->text;
|
|
}
|
|
|
|
|
|
drgui_tab* drgui_tab_get_next_tab(drgui_tab* pTab)
|
|
{
|
|
if (pTab == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTab->pNextTab;
|
|
}
|
|
|
|
drgui_tab* drgui_tab_get_prev_tab(drgui_tab* pTab)
|
|
{
|
|
if (pTab == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTab->pPrevTab;
|
|
}
|
|
|
|
|
|
void drgui_tab_move_to_front(drgui_tab* pTab)
|
|
{
|
|
if (pTab == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_element* pTBElement = pTab->pTBElement;
|
|
|
|
tab_detach_from_hierarchy(pTab);
|
|
tab_prepend(pTab, pTBElement);
|
|
}
|
|
|
|
bool drgui_tab_is_in_view(drgui_tab* pTab)
|
|
{
|
|
if (pTab == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_tabbar_is_tab_in_view(pTab->pTBElement, pTab);
|
|
}
|
|
|
|
void drgui_tab_move_into_view(drgui_tab* pTab)
|
|
{
|
|
if (!drgui_tab_is_in_view(pTab)) {
|
|
drgui_tab_move_to_front(pTab);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
DRGUI_PRIVATE void tab_append(drgui_tab* pTab, drgui_element* pTBElement)
|
|
{
|
|
if (pTab == NULL || pTBElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
pTab->pTBElement = pTBElement;
|
|
if (pTB->pFirstTab == NULL)
|
|
{
|
|
assert(pTB->pLastTab == NULL);
|
|
|
|
pTB->pFirstTab = pTab;
|
|
pTB->pLastTab = pTab;
|
|
}
|
|
else
|
|
{
|
|
assert(pTB->pLastTab != NULL);
|
|
|
|
pTab->pPrevTab = pTB->pLastTab;
|
|
|
|
pTB->pLastTab->pNextTab = pTab;
|
|
pTB->pLastTab = pTab;
|
|
}
|
|
|
|
|
|
if (pTB->isAutoSizeEnabled) {
|
|
drgui_tabbar_resize_by_tabs(pTBElement);
|
|
}
|
|
|
|
// The content of the menu has changed so we'll need to schedule a redraw.
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void tab_prepend(drgui_tab* pTab, drgui_element* pTBElement)
|
|
{
|
|
if (pTab == NULL || pTBElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
pTab->pTBElement = pTBElement;
|
|
if (pTB->pFirstTab == NULL)
|
|
{
|
|
assert(pTB->pLastTab == NULL);
|
|
|
|
pTB->pFirstTab = pTab;
|
|
pTB->pLastTab = pTab;
|
|
}
|
|
else
|
|
{
|
|
assert(pTB->pLastTab != NULL);
|
|
|
|
pTab->pNextTab = pTB->pFirstTab;
|
|
|
|
pTB->pFirstTab->pPrevTab = pTab;
|
|
pTB->pFirstTab = pTab;
|
|
}
|
|
|
|
|
|
if (pTB->isAutoSizeEnabled) {
|
|
drgui_tabbar_resize_by_tabs(pTBElement);
|
|
}
|
|
|
|
// The content of the menu has changed so we'll need to schedule a redraw.
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void tab_detach_from_hierarchy(drgui_tab* pTab)
|
|
{
|
|
if (pTab == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_element* pTBElement = pTab->pTBElement;
|
|
if (pTBElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
|
|
if (pTab->pNextTab != NULL) {
|
|
pTab->pNextTab->pPrevTab = pTab->pPrevTab;
|
|
}
|
|
|
|
if (pTab->pPrevTab != NULL) {
|
|
pTab->pPrevTab->pNextTab = pTab->pNextTab;
|
|
}
|
|
|
|
|
|
if (pTab == pTB->pFirstTab) {
|
|
pTB->pFirstTab = pTab->pNextTab;
|
|
}
|
|
|
|
if (pTab == pTB->pLastTab) {
|
|
pTB->pLastTab = pTab->pPrevTab;
|
|
}
|
|
|
|
|
|
pTab->pNextTab = NULL;
|
|
pTab->pPrevTab = NULL;
|
|
pTab->pTBElement = NULL;
|
|
}
|
|
|
|
DRGUI_PRIVATE void tab_detach(drgui_tab* pTab)
|
|
{
|
|
if (pTab == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_element* pTBElement = pTab->pTBElement;
|
|
if (pTBElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tab_bar* pTB = (drgui_tab_bar*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
if (pTB->pHoveredTab == pTab) {
|
|
pTB->pHoveredTab = NULL;
|
|
pTB->isCloseButtonHovered = false;
|
|
}
|
|
|
|
if (pTB->pActiveTab == pTab) {
|
|
pTB->pActiveTab = NULL;
|
|
}
|
|
|
|
if (pTB->pTabWithCloseButtonPressed == pTab) {
|
|
pTB->pTabWithCloseButtonPressed = NULL;
|
|
}
|
|
|
|
|
|
tab_detach_from_hierarchy(pTab);
|
|
|
|
|
|
if (pTB->isAutoSizeEnabled) {
|
|
drgui_tabbar_resize_by_tabs(pTBElement);
|
|
}
|
|
|
|
// The content of the menu has changed so we'll need to schedule a redraw.
|
|
if (drgui_is_auto_dirty_enabled(pTBElement->pContext)) {
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
}
|
|
#endif //DR_GUI_IMPLEMENTATION
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Text Box
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#ifndef DRGUI_NO_TEXT_EDITING
|
|
// QUICK NOTES
|
|
//
|
|
// - By default the cursor/caret does not blink automatically. Instead, the application must "step" the text box by
|
|
// calling drgui_textbox_step().
|
|
|
|
#ifndef drgui_textbox_h
|
|
#define drgui_textbox_h
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef void (* drgui_textbox_on_cursor_move_proc)(drgui_element* pTBElement);
|
|
typedef void (* drgui_textbox_on_undo_point_changed_proc)(drgui_element* pTBElement, unsigned int iUndoPoint);
|
|
|
|
|
|
/// Creates a new text box control.
|
|
drgui_element* drgui_create_textbox(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData);
|
|
|
|
/// Deletest the given text box control.
|
|
void drgui_delete_textbox(drgui_element* pTBElement);
|
|
|
|
|
|
/// Retrieves the size of the extra data associated with the given text box.
|
|
size_t drgui_textbox_get_extra_data_size(drgui_element* pTBElement);
|
|
|
|
/// Retrieves a pointer to the extra data associated with the given text box.
|
|
void* drgui_textbox_get_extra_data(drgui_element* pTBElement);
|
|
|
|
|
|
/// Sets the font to use with the given text box.
|
|
void drgui_textbox_set_font(drgui_element* pTBElement, drgui_font* pFont);
|
|
|
|
/// Retrieves the font being used with the given text box.
|
|
drgui_font* drgui_textbox_get_font(drgui_element* pTBElement);
|
|
|
|
/// Sets the color of the text in teh given text box.
|
|
void drgui_textbox_set_text_color(drgui_element* pTBElement, drgui_color color);
|
|
|
|
/// Sets the background color of the given text box.
|
|
void drgui_textbox_set_background_color(drgui_element* pTBElement, drgui_color color);
|
|
|
|
/// Sets the background color of selected text.
|
|
void drgui_textbox_set_selection_background_color(drgui_element* pTBElement, drgui_color color);
|
|
|
|
/// Retrieves the background color of selected text.
|
|
drgui_color drgui_textbox_get_selection_background_color(drgui_element* pTBElement);
|
|
|
|
/// Sets the background color for the line the caret is currently sitting on.
|
|
void drgui_textbox_set_active_line_background_color(drgui_element* pTBElement, drgui_color color);
|
|
|
|
/// Sets the width of the text cursor.
|
|
void drgui_textbox_set_cursor_width(drgui_element* pTBElement, float cursorWidth);
|
|
|
|
/// Retrieves the width of the text cursor.
|
|
float drgui_textbox_get_cursor_width(drgui_element* pTBElement);
|
|
|
|
/// Sets the color of the cursor of the given text box.
|
|
void drgui_textbox_set_cursor_color(drgui_element* pTBElement, drgui_color color);
|
|
|
|
/// Sets the border color of the given text box.
|
|
void drgui_textbox_set_border_color(drgui_element* pTBElement, drgui_color color);
|
|
|
|
/// Sets the border width of the given text box.
|
|
void drgui_textbox_set_border_width(drgui_element* pTBElement, float borderWidth);
|
|
|
|
/// Sets the amount of padding to apply to given text box.
|
|
void drgui_textbox_set_padding(drgui_element* pTBElement, float padding);
|
|
|
|
/// Retrieves the amound of vertical padding to apply to the given text box.
|
|
float drgui_textbox_get_padding_vert(drgui_element* pTBElement);
|
|
|
|
/// Retrieves the amound of horizontal padding to apply to the given text box.
|
|
float drgui_textbox_get_padding_horz(drgui_element* pTBElement);
|
|
|
|
/// Sets the vertical alignment of the given text box.
|
|
void drgui_textbox_set_vertical_align(drgui_element* pTBElement, drgui_text_engine_alignment align);
|
|
|
|
/// Sets the horizontal alignment of the given text box.
|
|
void drgui_textbox_set_horizontal_align(drgui_element* pTBElement, drgui_text_engine_alignment align);
|
|
|
|
// Sets the width of the line numbers.
|
|
void drgui_textbox_set_line_numbers_width(drgui_element* pTBElement, float lineNumbersWidth);
|
|
|
|
// Retrieves the width of the line numbers.
|
|
float drgui_textbox_get_line_numbers_width(drgui_element* pTBElement);
|
|
|
|
// Sets the padding to apply between the line numbers and the text.
|
|
void drgui_textbox_set_line_numbers_padding(drgui_element* pTBElement, float lineNumbersPadding);
|
|
|
|
// Retrieves the padding to apply between the line numbers and the text.
|
|
float drgui_textbox_get_line_numbers_padding(drgui_element* pTBElement);
|
|
|
|
// Sets the color of the text of the line numbers.
|
|
void drgui_textbox_set_line_numbers_color(drgui_element* pTBElement, drgui_color color);
|
|
|
|
// Retrieves the color of the text of the line numbers.
|
|
drgui_color drgui_textbox_get_line_numbers_color(drgui_element* pTBElement);
|
|
|
|
// Sets the color of the background of the line numbers.
|
|
void drgui_textbox_set_line_numbers_background_color(drgui_element* pTBElement, drgui_color color);
|
|
|
|
// Retrieves the color of the background of the line numbers.
|
|
drgui_color drgui_textbox_get_line_numbers_background_color(drgui_element* pTBElement);
|
|
|
|
|
|
/// Sets the text of the given text box.
|
|
void drgui_textbox_set_text(drgui_element* pTBElement, const char* text);
|
|
|
|
/// Retrieves the text of the given text box.
|
|
size_t drgui_textbox_get_text(drgui_element* pTBElement, char* pTextOut, size_t textOutSize);
|
|
|
|
/// Steps the text box to allow it to blink the cursor.
|
|
void drgui_textbox_step(drgui_element* pTBElement, unsigned int milliseconds);
|
|
|
|
/// Sets the blink rate of the cursor in milliseconds.
|
|
void drgui_textbox_set_cursor_blink_rate(drgui_element* pTBElement, unsigned int blinkRateInMilliseconds);
|
|
|
|
/// Moves the caret to the end of the text.
|
|
void drgui_textbox_move_cursor_to_end_of_text(drgui_element* pTBElement);
|
|
|
|
/// Moves the caret to the beginning of the line at the given index.
|
|
void drgui_textbox_move_cursor_to_start_of_line_by_index(drgui_element* pTBElement, size_t iLine);
|
|
|
|
/// Determines whether or not anything is selected in the given text box.
|
|
bool drgui_textbox_is_anything_selected(drgui_element* pTBElement);
|
|
|
|
/// Selects all of the text inside the text box.
|
|
void drgui_textbox_select_all(drgui_element* pTBElement);
|
|
|
|
/// Deselect everything.
|
|
void drgui_textbox_deselect_all(drgui_element* pTBElement);
|
|
|
|
/// Retrieves a copy of the selected text.
|
|
///
|
|
/// @remarks
|
|
/// This returns the length of the selected text. Call this once with <textOut> set to NULL to calculate the required size of the
|
|
/// buffer.
|
|
/// @par
|
|
/// If the output buffer is not larger enough, the string will be truncated.
|
|
size_t drgui_textbox_get_selected_text(drgui_element* pTBElement, char* textOut, size_t textOutLength);
|
|
|
|
/// Deletes the character to the right of the cursor.
|
|
///
|
|
/// @return True if the text within the text engine has changed.
|
|
bool drgui_textbox_delete_character_to_right_of_cursor(drgui_element* pTBElement);
|
|
|
|
/// Deletes the currently selected text.
|
|
///
|
|
/// @return True if the text within the text engine has changed.
|
|
bool drgui_textbox_delete_selected_text(drgui_element* pTBElement);
|
|
|
|
/// Inserts a character at the position of the cursor.
|
|
///
|
|
/// @return True if the text within the text engine has changed.
|
|
bool drgui_textbox_insert_text_at_cursor(drgui_element* pTBElement, const char* text);
|
|
|
|
/// Performs an undo operation.
|
|
bool drgui_textbox_undo(drgui_element* pTBElement);
|
|
|
|
/// Performs a redo operation.
|
|
bool drgui_textbox_redo(drgui_element* pTBElement);
|
|
|
|
/// Retrieves the number of undo points remaining.
|
|
unsigned int drgui_textbox_get_undo_points_remaining_count(drgui_element* pTBElement);
|
|
|
|
/// Retrieves the number of redo points remaining.
|
|
unsigned int drgui_textbox_get_redo_points_remaining_count(drgui_element* pTBElement);
|
|
|
|
/// Clears the undo/redo stack.
|
|
void drgui_textbox_clear_undo_stack(drgui_element* pTBElement);
|
|
|
|
/// Retrieves the index of the line the cursor is current sitting on.
|
|
size_t drgui_textbox_get_cursor_line(drgui_element* pTBElement);
|
|
|
|
/// Retrieves the index of the column the cursor is current sitting on.
|
|
size_t drgui_textbox_get_cursor_column(drgui_element* pTBElement);
|
|
|
|
/// Retrieves the number of lines in the given text box.
|
|
size_t drgui_textbox_get_line_count(drgui_element* pTBElement);
|
|
|
|
|
|
/// Finds and selects the next occurance of the given string, starting from the cursor and looping back to the start.
|
|
bool drgui_textbox_find_and_select_next(drgui_element* pTBElement, const char* text);
|
|
|
|
/// Finds the next occurance of the given string and replaces it with another.
|
|
bool drgui_textbox_find_and_replace_next(drgui_element* pTBElement, const char* text, const char* replacement);
|
|
|
|
/// Finds every occurance of the given string and replaces it with another.
|
|
bool drgui_textbox_find_and_replace_all(drgui_element* pTBElement, const char* text, const char* replacement);
|
|
|
|
|
|
/// Shows the line numbers.
|
|
void drgui_textbox_show_line_numbers(drgui_element* pTBElement);
|
|
|
|
/// Hides the line numbers.
|
|
void drgui_textbox_hide_line_numbers(drgui_element* pTBElement);
|
|
|
|
|
|
/// Disables the vertical scrollbar.
|
|
void drgui_textbox_disable_vertical_scrollbar(drgui_element* pTBElement);
|
|
|
|
/// Enables the vertical scrollbar.
|
|
void drgui_textbox_enable_vertical_scrollbar(drgui_element* pTBElement);
|
|
|
|
/// Disables the horizontal scrollbar.
|
|
void drgui_textbox_disable_horizontal_scrollbar(drgui_element* pTBElement);
|
|
|
|
/// Enables the horizontal scrollbar.
|
|
void drgui_textbox_enable_horizontal_scrollbar(drgui_element* pTBElement);
|
|
|
|
// Retrieves the vertical scrollbar.
|
|
drgui_element* drgui_textbox_get_vertical_scrollbar(drgui_element* pTBElement);
|
|
|
|
// Retrieves the horizontal scrollbar.
|
|
drgui_element* drgui_textbox_get_horizontal_scrollbar(drgui_element* pTBElement);
|
|
|
|
// Sets the size of both the vertical and horizontal scrollbars.
|
|
void drgui_textbox_set_scrollbar_size(drgui_element* pTBElement, float size);
|
|
|
|
|
|
/// Sets the function to call when the cursor moves.
|
|
void drgui_textbox_set_on_cursor_move(drgui_element* pTBElement, drgui_textbox_on_cursor_move_proc proc);
|
|
|
|
/// Sets the function to call when the undo point changes.
|
|
void drgui_textbox_set_on_undo_point_changed(drgui_element* pTBElement, drgui_textbox_on_undo_point_changed_proc proc);
|
|
|
|
|
|
|
|
/// on_size.
|
|
void drgui_textbox_on_size(drgui_element* pTBElement, float newWidth, float newHeight);
|
|
|
|
/// on_mouse_move.
|
|
void drgui_textbox_on_mouse_move(drgui_element* pTBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// on_mouse_button_down.
|
|
void drgui_textbox_on_mouse_button_down(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// on_mouse_button_up.
|
|
void drgui_textbox_on_mouse_button_up(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// on_mouse_button_dblclick.
|
|
void drgui_textbox_on_mouse_button_dblclick(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// on_mouse_wheel
|
|
void drgui_textbox_on_mouse_wheel(drgui_element* pTBElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// on_key_down.
|
|
void drgui_textbox_on_key_down(drgui_element* pTBElement, drgui_key key, int stateFlags);
|
|
|
|
/// on_key_up.
|
|
void drgui_textbox_on_key_up(drgui_element* pTBElement, drgui_key key, int stateFlags);
|
|
|
|
/// on_printable_key_down.
|
|
void drgui_textbox_on_printable_key_down(drgui_element* pTBElement, unsigned int utf32, int stateFlags);
|
|
|
|
/// on_paint.
|
|
void drgui_textbox_on_paint(drgui_element* pTBElement, drgui_rect relativeRect, void* pPaintData);
|
|
|
|
/// on_capture_keyboard
|
|
void drgui_textbox_on_capture_keyboard(drgui_element* pTBElement, drgui_element* pPrevCapturedElement);
|
|
|
|
/// on_release_keyboard
|
|
void drgui_textbox_on_release_keyboard(drgui_element* pTBElement, drgui_element* pNewCapturedElement);
|
|
|
|
/// on_capture_mouse
|
|
void drgui_textbox_on_capture_mouse(drgui_element* pTBElement);
|
|
|
|
/// on_release_mouse
|
|
void drgui_textbox_on_release_mouse(drgui_element* pTBElement);
|
|
|
|
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif //drgui_textbox_h
|
|
|
|
|
|
#ifdef DR_GUI_IMPLEMENTATION
|
|
typedef struct
|
|
{
|
|
/// The text engine.
|
|
drgui_text_engine* pTL;
|
|
|
|
/// The vertical scrollbar.
|
|
drgui_element* pVertScrollbar;
|
|
|
|
/// The horizontal scrollbar.
|
|
drgui_element* pHorzScrollbar;
|
|
|
|
/// The line numbers element.
|
|
drgui_element* pLineNumbers;
|
|
|
|
|
|
/// The color of the border.
|
|
drgui_color borderColor;
|
|
|
|
/// The width of the border.
|
|
float borderWidth;
|
|
|
|
/// The amount of padding to apply the left and right of the text.
|
|
float padding;
|
|
|
|
// The width of the line numbers.
|
|
float lineNumbersWidth;
|
|
|
|
/// The padding to the right of the line numbers.
|
|
float lineNumbersPaddingRight;
|
|
|
|
// The color of the text of the line numbers.
|
|
drgui_color lineNumbersColor;
|
|
|
|
// The color of the background of the line numbers.
|
|
drgui_color lineNumbersBackgroundColor;
|
|
|
|
|
|
/// The desired width of the vertical scrollbar.
|
|
float vertScrollbarSize;
|
|
|
|
/// The desired height of the horizontal scrollbar.
|
|
float horzScrollbarSize;
|
|
|
|
/// Whether or not the vertical scrollbar is enabled.
|
|
bool isVertScrollbarEnabled;
|
|
|
|
/// Whether or not the horizontal scrollbar is enabled.
|
|
bool isHorzScrollbarEnabled;
|
|
|
|
|
|
/// When selecting lines by clicking and dragging on the line numbers, keeps track of the line to anchor the selection to.
|
|
size_t iLineSelectAnchor;
|
|
|
|
|
|
/// The function to call when the text cursor/caret moves.
|
|
drgui_textbox_on_cursor_move_proc onCursorMove;
|
|
|
|
/// The function to call when the undo point changes.
|
|
drgui_textbox_on_undo_point_changed_proc onUndoPointChanged;
|
|
|
|
|
|
/// The size of the extra data.
|
|
size_t extraDataSize;
|
|
|
|
/// A pointer to the extra data.
|
|
char pExtraData[1];
|
|
|
|
} drgui_textbox;
|
|
|
|
|
|
/// Retrieves the offset to draw the text in the text box.
|
|
DRGUI_PRIVATE void drgui_textbox__get_text_offset(drgui_element* pTBElement, float* pOffsetXOut, float* pOffsetYOut);
|
|
|
|
/// Calculates the required size of the text engine.
|
|
DRGUI_PRIVATE void drgui_textbox__calculate_text_engine_container_size(drgui_element* pTBElement, float* pWidthOut, float* pHeightOut);
|
|
|
|
/// Retrieves the rectangle of the text engine's container.
|
|
DRGUI_PRIVATE drgui_rect drgui_textbox__get_text_rect(drgui_element* pTBElement);
|
|
|
|
/// Refreshes the range, page sizes and layouts of the scrollbars.
|
|
DRGUI_PRIVATE void drgui_textbox__refresh_scrollbars(drgui_element* pTBElement);
|
|
|
|
/// Refreshes the range and page sizes of the scrollbars.
|
|
DRGUI_PRIVATE void drgui_textbox__refresh_scrollbar_ranges(drgui_element* pTBElement);
|
|
|
|
/// Refreshes the size and position of the scrollbars.
|
|
DRGUI_PRIVATE void drgui_textbox__refresh_scrollbar_layouts(drgui_element* pTBElement);
|
|
|
|
/// Retrieves a rectangle representing the space between the edges of the two scrollbars.
|
|
DRGUI_PRIVATE drgui_rect drgui_textbox__get_scrollbar_dead_space_rect(drgui_element* pTBElement);
|
|
|
|
|
|
/// Called when a mouse button is pressed on the line numbers element.
|
|
DRGUI_PRIVATE void drgui_textbox__on_mouse_move_line_numbers(drgui_element* pLineNumbers, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when a mouse button is pressed on the line numbers element.
|
|
DRGUI_PRIVATE void drgui_textbox__on_mouse_button_down_line_numbers(drgui_element* pLineNumbers, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when a mouse button is pressed on the line numbers element.
|
|
DRGUI_PRIVATE void drgui_textbox__on_mouse_button_up_line_numbers(drgui_element* pLineNumbers, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the line numbers element needs to be drawn.
|
|
DRGUI_PRIVATE void drgui_textbox__on_paint_line_numbers(drgui_element* pLineNumbers, drgui_rect relativeRect, void* pPaintData);
|
|
|
|
/// Refreshes the line number of the given text editor.
|
|
DRGUI_PRIVATE void drgui_textbox__refresh_line_numbers(drgui_element* pTBElement);
|
|
|
|
|
|
/// on_paint_rect()
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_paint_rect(drgui_text_engine* pLayout, drgui_rect rect, drgui_color color, drgui_element* pTBElement, void* pPaintData);
|
|
|
|
/// on_paint_text()
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_paint_text(drgui_text_engine* pTL, drgui_text_run* pRun, drgui_element* pTBElement, void* pPaintData);
|
|
|
|
/// on_dirty()
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_dirty(drgui_text_engine* pTL, drgui_rect rect);
|
|
|
|
/// on_cursor_move()
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_cursor_move(drgui_text_engine* pTL);
|
|
|
|
/// on_text_changed()
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_text_changed(drgui_text_engine* pTL);
|
|
|
|
/// on_undo_point_changed()
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_undo_point_changed(drgui_text_engine* pTL, unsigned int iUndoPoint);
|
|
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_vscroll(drgui_element* pSBElement, int scrollPos)
|
|
{
|
|
drgui_element* pTBElement = *(drgui_element**)drgui_sb_get_extra_data(pSBElement);
|
|
assert(pTBElement != NULL);
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
drgui_text_engine_set_inner_offset_y(pTB->pTL, -drgui_text_engine_get_line_pos_y(pTB->pTL, scrollPos));
|
|
|
|
// The line numbers need to be redrawn.
|
|
drgui_dirty(pTB->pLineNumbers, drgui_get_local_rect(pTB->pLineNumbers));
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_hscroll(drgui_element* pSBElement, int scrollPos)
|
|
{
|
|
drgui_element* pTBElement = *(drgui_element**)drgui_sb_get_extra_data(pSBElement);
|
|
assert(pTBElement != NULL);
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
drgui_text_engine_set_inner_offset_x(pTB->pTL, (float)-scrollPos);
|
|
}
|
|
|
|
|
|
|
|
drgui_element* drgui_create_textbox(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData)
|
|
{
|
|
if (pContext == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_element* pTBElement = drgui_create_element(pContext, pParent, sizeof(drgui_textbox) + extraDataSize, NULL);
|
|
if (pTBElement == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_set_cursor(pTBElement, drgui_cursor_text);
|
|
drgui_set_on_size(pTBElement, drgui_textbox_on_size);
|
|
drgui_set_on_mouse_move(pTBElement, drgui_textbox_on_mouse_move);
|
|
drgui_set_on_mouse_button_down(pTBElement, drgui_textbox_on_mouse_button_down);
|
|
drgui_set_on_mouse_button_up(pTBElement, drgui_textbox_on_mouse_button_up);
|
|
drgui_set_on_mouse_button_dblclick(pTBElement, drgui_textbox_on_mouse_button_dblclick);
|
|
drgui_set_on_mouse_wheel(pTBElement, drgui_textbox_on_mouse_wheel);
|
|
drgui_set_on_key_down(pTBElement, drgui_textbox_on_key_down);
|
|
drgui_set_on_printable_key_down(pTBElement, drgui_textbox_on_printable_key_down);
|
|
drgui_set_on_paint(pTBElement, drgui_textbox_on_paint);
|
|
drgui_set_on_capture_keyboard(pTBElement, drgui_textbox_on_capture_keyboard);
|
|
drgui_set_on_release_keyboard(pTBElement, drgui_textbox_on_release_keyboard);
|
|
drgui_set_on_capture_mouse(pTBElement, drgui_textbox_on_capture_mouse);
|
|
drgui_set_on_release_mouse(pTBElement, drgui_textbox_on_release_mouse);
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
pTB->pVertScrollbar = drgui_create_scrollbar(pContext, pTBElement, drgui_sb_orientation_vertical, sizeof(pTBElement), &pTBElement);
|
|
drgui_sb_set_on_scroll(pTB->pVertScrollbar, drgui_textbox__on_vscroll);
|
|
drgui_sb_set_mouse_wheel_scele(pTB->pVertScrollbar, 3);
|
|
|
|
pTB->pHorzScrollbar = drgui_create_scrollbar(pContext, pTBElement, drgui_sb_orientation_horizontal, sizeof(pTBElement), &pTBElement);
|
|
drgui_sb_set_on_scroll(pTB->pHorzScrollbar, drgui_textbox__on_hscroll);
|
|
|
|
pTB->pLineNumbers = drgui_create_element(pContext, pTBElement, sizeof(pTBElement), &pTBElement);
|
|
drgui_hide(pTB->pLineNumbers);
|
|
drgui_set_on_mouse_move(pTB->pLineNumbers, drgui_textbox__on_mouse_move_line_numbers);
|
|
drgui_set_on_mouse_button_down(pTB->pLineNumbers, drgui_textbox__on_mouse_button_down_line_numbers);
|
|
drgui_set_on_mouse_button_up(pTB->pLineNumbers, drgui_textbox__on_mouse_button_up_line_numbers);
|
|
drgui_set_on_paint(pTB->pLineNumbers, drgui_textbox__on_paint_line_numbers);
|
|
|
|
pTB->pTL = drgui_create_text_engine(pContext, sizeof(pTBElement), &pTBElement);
|
|
if (pTB->pTL == NULL) {
|
|
drgui_delete_element(pTBElement);
|
|
return NULL;
|
|
}
|
|
|
|
drgui_text_engine_set_on_paint_rect(pTB->pTL, drgui_textbox__on_text_engine_paint_rect);
|
|
drgui_text_engine_set_on_paint_text(pTB->pTL, drgui_textbox__on_text_engine_paint_text);
|
|
drgui_text_engine_set_on_dirty(pTB->pTL, drgui_textbox__on_text_engine_dirty);
|
|
drgui_text_engine_set_on_cursor_move(pTB->pTL, drgui_textbox__on_text_engine_cursor_move);
|
|
drgui_text_engine_set_on_text_changed(pTB->pTL, drgui_textbox__on_text_engine_text_changed);
|
|
drgui_text_engine_set_on_undo_point_changed(pTB->pTL, drgui_textbox__on_text_engine_undo_point_changed);
|
|
drgui_text_engine_set_default_text_color(pTB->pTL, drgui_rgb(0, 0, 0));
|
|
drgui_text_engine_set_cursor_color(pTB->pTL, drgui_rgb(0, 0, 0));
|
|
drgui_text_engine_set_default_bg_color(pTB->pTL, drgui_rgb(255, 255, 255));
|
|
drgui_text_engine_set_active_line_bg_color(pTB->pTL, drgui_rgb(255, 255, 255));
|
|
drgui_text_engine_set_vertical_align(pTB->pTL, drgui_text_engine_alignment_center);
|
|
|
|
pTB->borderColor = drgui_rgb(0, 0, 0);
|
|
pTB->borderWidth = 1;
|
|
pTB->padding = 2;
|
|
pTB->lineNumbersWidth = 64;
|
|
pTB->lineNumbersPaddingRight = 16;
|
|
pTB->lineNumbersColor = drgui_rgb(80, 160, 192);
|
|
pTB->lineNumbersBackgroundColor = drgui_text_engine_get_default_bg_color(pTB->pTL);
|
|
pTB->vertScrollbarSize = 16;
|
|
pTB->horzScrollbarSize = 16;
|
|
pTB->isVertScrollbarEnabled = true;
|
|
pTB->isHorzScrollbarEnabled = true;
|
|
pTB->iLineSelectAnchor = 0;
|
|
pTB->onCursorMove = NULL;
|
|
pTB->onUndoPointChanged = NULL;
|
|
|
|
pTB->extraDataSize = extraDataSize;
|
|
if (pExtraData != NULL) {
|
|
memcpy(pTB->pExtraData, pExtraData, extraDataSize);
|
|
}
|
|
|
|
return pTBElement;
|
|
}
|
|
|
|
void drgui_delete_textbox(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTB->pTL) {
|
|
drgui_delete_text_engine(pTB->pTL);
|
|
pTB->pTL = NULL;
|
|
}
|
|
|
|
if (pTB->pLineNumbers) {
|
|
drgui_delete_element(pTB->pLineNumbers);
|
|
pTB->pLineNumbers = NULL;
|
|
}
|
|
|
|
if (pTB->pHorzScrollbar) {
|
|
drgui_delete_element(pTB->pHorzScrollbar);
|
|
pTB->pHorzScrollbar = NULL;
|
|
}
|
|
|
|
if (pTB->pVertScrollbar) {
|
|
drgui_delete_element(pTB->pVertScrollbar);
|
|
pTB->pVertScrollbar = NULL;
|
|
}
|
|
|
|
drgui_delete_element(pTBElement);
|
|
}
|
|
|
|
size_t drgui_textbox_get_extra_data_size(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTB->extraDataSize;
|
|
}
|
|
|
|
void* drgui_textbox_get_extra_data(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTB->pExtraData;
|
|
}
|
|
|
|
|
|
void drgui_textbox_set_font(drgui_element* pTBElement, drgui_font* pFont)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_begin_dirty(pTBElement);
|
|
{
|
|
drgui_text_engine_set_default_font(pTB->pTL, pFont);
|
|
|
|
// The font used for line numbers are tied to the main font at the moment.
|
|
drgui_textbox__refresh_line_numbers(pTBElement);
|
|
|
|
// Emulate a scroll to ensure the scroll position is pinned to a line.
|
|
drgui_textbox__on_vscroll(pTB->pVertScrollbar, drgui_sb_get_scroll_position(pTB->pVertScrollbar));
|
|
drgui_textbox__refresh_scrollbars(pTBElement);
|
|
|
|
// The caret position needs to be refreshes. We'll cheat here a little bit and just do a full refresh of the text engine.
|
|
//drgui_text_engine__refresh(pTB->pTL);
|
|
drgui_text_engine_refresh_markers(pTB->pTL);
|
|
}
|
|
drgui_end_dirty(pTBElement);
|
|
}
|
|
|
|
drgui_font* drgui_textbox_get_font(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return drgui_text_engine_get_default_font(pTB->pTL);
|
|
}
|
|
|
|
void drgui_textbox_set_text_color(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_set_default_text_color(pTB->pTL, color);
|
|
}
|
|
|
|
void drgui_textbox_set_background_color(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_set_default_bg_color(pTB->pTL, color);
|
|
}
|
|
|
|
void drgui_textbox_set_selection_background_color(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_set_selection_bg_color(pTB->pTL, color);
|
|
}
|
|
|
|
drgui_color drgui_textbox_get_selection_background_color(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return drgui_text_engine_get_selection_bg_color(pTB->pTL);
|
|
}
|
|
|
|
void drgui_textbox_set_active_line_background_color(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_set_active_line_bg_color(pTB->pTL, color);
|
|
}
|
|
|
|
void drgui_textbox_set_cursor_width(drgui_element* pTBElement, float cursorWidth)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_set_cursor_width(pTB->pTL, cursorWidth);
|
|
}
|
|
|
|
float drgui_textbox_get_cursor_width(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return drgui_text_engine_get_cursor_width(pTB->pTL);
|
|
}
|
|
|
|
void drgui_textbox_set_cursor_color(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_set_cursor_color(pTB->pTL, color);
|
|
}
|
|
|
|
void drgui_textbox_set_border_color(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->borderColor = color;
|
|
}
|
|
|
|
void drgui_textbox_set_border_width(drgui_element* pTBElement, float borderWidth)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->borderWidth = borderWidth;
|
|
}
|
|
|
|
void drgui_textbox_set_padding(drgui_element* pTBElement, float padding)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->padding = padding;
|
|
}
|
|
|
|
float drgui_textbox_get_padding_vert(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTB->padding;
|
|
}
|
|
|
|
float drgui_textbox_get_padding_horz(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTB->padding;
|
|
}
|
|
|
|
void drgui_textbox_set_vertical_align(drgui_element* pTBElement, drgui_text_engine_alignment align)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_set_vertical_align(pTB->pTL, align);
|
|
}
|
|
|
|
void drgui_textbox_set_horizontal_align(drgui_element* pTBElement, drgui_text_engine_alignment align)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_set_horizontal_align(pTB->pTL, align);
|
|
}
|
|
|
|
void drgui_textbox_set_line_numbers_width(drgui_element* pTBElement, float lineNumbersWidth)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->lineNumbersWidth = lineNumbersWidth;
|
|
}
|
|
|
|
float drgui_textbox_get_line_numbers_width(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTB->lineNumbersWidth;
|
|
}
|
|
|
|
void drgui_textbox_set_line_numbers_padding(drgui_element* pTBElement, float lineNumbersPadding)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->lineNumbersPaddingRight = lineNumbersPadding;
|
|
}
|
|
|
|
float drgui_textbox_get_line_numbers_padding(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTB->lineNumbersPaddingRight;
|
|
}
|
|
|
|
void drgui_textbox_set_line_numbers_color(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->lineNumbersColor = color;
|
|
drgui_textbox__refresh_line_numbers(pTBElement);
|
|
}
|
|
|
|
drgui_color drgui_textbox_get_line_numbers_color(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTB->lineNumbersColor;
|
|
}
|
|
|
|
void drgui_textbox_set_line_numbers_background_color(drgui_element* pTBElement, drgui_color color)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->lineNumbersBackgroundColor = color;
|
|
drgui_textbox__refresh_line_numbers(pTBElement);
|
|
}
|
|
|
|
drgui_color drgui_textbox_get_line_numbers_background_color(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTB->lineNumbersBackgroundColor;
|
|
}
|
|
|
|
|
|
|
|
void drgui_textbox_set_text(drgui_element* pTBElement, const char* text)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_prepare_undo_point(pTB->pTL);
|
|
{
|
|
drgui_text_engine_set_text(pTB->pTL, text);
|
|
}
|
|
drgui_text_engine_commit_undo_point(pTB->pTL);
|
|
}
|
|
|
|
size_t drgui_textbox_get_text(drgui_element* pTBElement, char* pTextOut, size_t textOutSize)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return drgui_text_engine_get_text(pTB->pTL, pTextOut, textOutSize);
|
|
}
|
|
|
|
void drgui_textbox_step(drgui_element* pTBElement, unsigned int milliseconds)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_step(pTB->pTL, milliseconds);
|
|
}
|
|
|
|
void drgui_textbox_set_cursor_blink_rate(drgui_element* pTBElement, unsigned int blinkRateInMilliseconds)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_set_cursor_blink_rate(pTB->pTL, blinkRateInMilliseconds);
|
|
}
|
|
|
|
void drgui_textbox_move_cursor_to_end_of_text(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_move_cursor_to_end_of_text(pTB->pTL);
|
|
}
|
|
|
|
void drgui_textbox_move_cursor_to_start_of_line_by_index(drgui_element* pTBElement, size_t iLine)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_move_cursor_to_start_of_line_by_index(pTB->pTL, iLine);
|
|
}
|
|
|
|
|
|
bool drgui_textbox_is_anything_selected(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_text_engine_is_anything_selected(pTB->pTL);
|
|
}
|
|
|
|
void drgui_textbox_select_all(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_select_all(pTB->pTL);
|
|
}
|
|
|
|
void drgui_textbox_deselect_all(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_deselect_all(pTB->pTL);
|
|
}
|
|
|
|
size_t drgui_textbox_get_selected_text(drgui_element* pTBElement, char* textOut, size_t textOutLength)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return drgui_text_engine_get_selected_text(pTB->pTL, textOut, textOutLength);
|
|
}
|
|
|
|
bool drgui_textbox_delete_character_to_right_of_cursor(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
bool wasTextChanged = false;
|
|
drgui_text_engine_prepare_undo_point(pTB->pTL);
|
|
{
|
|
wasTextChanged = drgui_text_engine_delete_character_to_right_of_cursor(pTB->pTL);
|
|
}
|
|
if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); }
|
|
|
|
return wasTextChanged;
|
|
}
|
|
|
|
bool drgui_textbox_delete_selected_text(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
bool wasTextChanged = false;
|
|
drgui_text_engine_prepare_undo_point(pTB->pTL);
|
|
{
|
|
wasTextChanged = drgui_text_engine_delete_selected_text(pTB->pTL);
|
|
}
|
|
if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); }
|
|
|
|
return wasTextChanged;
|
|
}
|
|
|
|
bool drgui_textbox_insert_text_at_cursor(drgui_element* pTBElement, const char* text)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return false;;
|
|
}
|
|
|
|
bool wasTextChanged = false;
|
|
drgui_text_engine_prepare_undo_point(pTB->pTL);
|
|
{
|
|
wasTextChanged = drgui_text_engine_insert_text_at_cursor(pTB->pTL, text);
|
|
}
|
|
if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); }
|
|
|
|
return wasTextChanged;
|
|
}
|
|
|
|
bool drgui_textbox_undo(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_text_engine_undo(pTB->pTL);
|
|
}
|
|
|
|
bool drgui_textbox_redo(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_text_engine_redo(pTB->pTL);
|
|
}
|
|
|
|
unsigned int drgui_textbox_get_undo_points_remaining_count(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_text_engine_get_undo_points_remaining_count(pTB->pTL);
|
|
}
|
|
|
|
unsigned int drgui_textbox_get_redo_points_remaining_count(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return drgui_text_engine_get_redo_points_remaining_count(pTB->pTL);
|
|
}
|
|
|
|
void drgui_textbox_clear_undo_stack(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_clear_undo_stack(pTB->pTL);
|
|
}
|
|
|
|
|
|
size_t drgui_textbox_get_cursor_line(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return drgui_text_engine_get_cursor_line(pTB->pTL);
|
|
}
|
|
|
|
size_t drgui_textbox_get_cursor_column(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return drgui_text_engine_get_cursor_column(pTB->pTL);
|
|
}
|
|
|
|
size_t drgui_textbox_get_line_count(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return drgui_text_engine_get_line_count(pTB->pTL);
|
|
}
|
|
|
|
|
|
bool drgui_textbox_find_and_select_next(drgui_element* pTBElement, const char* text)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
size_t selectionStart;
|
|
size_t selectionEnd;
|
|
if (drgui_text_engine_find_next(pTB->pTL, text, &selectionStart, &selectionEnd))
|
|
{
|
|
drgui_text_engine_select(pTB->pTL, selectionStart, selectionEnd);
|
|
drgui_text_engine_move_cursor_to_end_of_selection(pTB->pTL);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool drgui_textbox_find_and_replace_next(drgui_element* pTBElement, const char* text, const char* replacement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
bool wasTextChanged = false;
|
|
drgui_text_engine_prepare_undo_point(pTB->pTL);
|
|
{
|
|
size_t selectionStart;
|
|
size_t selectionEnd;
|
|
if (drgui_text_engine_find_next(pTB->pTL, text, &selectionStart, &selectionEnd))
|
|
{
|
|
drgui_text_engine_select(pTB->pTL, selectionStart, selectionEnd);
|
|
drgui_text_engine_move_cursor_to_end_of_selection(pTB->pTL);
|
|
|
|
wasTextChanged = drgui_text_engine_delete_selected_text(pTB->pTL) || wasTextChanged;
|
|
wasTextChanged = drgui_text_engine_insert_text_at_cursor(pTB->pTL, replacement) || wasTextChanged;
|
|
}
|
|
}
|
|
if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); }
|
|
|
|
return wasTextChanged;
|
|
}
|
|
|
|
bool drgui_textbox_find_and_replace_all(drgui_element* pTBElement, const char* text, const char* replacement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
size_t originalCursorLine = drgui_text_engine_get_cursor_line(pTB->pTL);
|
|
size_t originalCursorPos = drgui_text_engine_get_cursor_character(pTB->pTL) - drgui_text_engine_get_line_first_character(pTB->pTL, originalCursorLine);
|
|
int originalScrollPosX = drgui_sb_get_scroll_position(pTB->pHorzScrollbar);
|
|
int originalScrollPosY = drgui_sb_get_scroll_position(pTB->pVertScrollbar);
|
|
|
|
bool wasTextChanged = false;
|
|
drgui_text_engine_prepare_undo_point(pTB->pTL);
|
|
{
|
|
// It's important that we don't replace the replacement text. To handle this, we just move the cursor to the top of the text and find
|
|
// and replace every occurance without looping.
|
|
drgui_text_engine_move_cursor_to_start_of_text(pTB->pTL);
|
|
|
|
size_t selectionStart;
|
|
size_t selectionEnd;
|
|
while (drgui_text_engine_find_next_no_loop(pTB->pTL, text, &selectionStart, &selectionEnd))
|
|
{
|
|
drgui_text_engine_select(pTB->pTL, selectionStart, selectionEnd);
|
|
drgui_text_engine_move_cursor_to_end_of_selection(pTB->pTL);
|
|
|
|
wasTextChanged = drgui_text_engine_delete_selected_text(pTB->pTL) || wasTextChanged;
|
|
wasTextChanged = drgui_text_engine_insert_text_at_cursor(pTB->pTL, replacement) || wasTextChanged;
|
|
}
|
|
|
|
// The cursor may have moved so we'll need to restore it.
|
|
size_t lineCharStart;
|
|
size_t lineCharEnd;
|
|
drgui_text_engine_get_line_character_range(pTB->pTL, originalCursorLine, &lineCharStart, &lineCharEnd);
|
|
|
|
size_t newCursorPos = lineCharStart + originalCursorPos;
|
|
if (newCursorPos > lineCharEnd) {
|
|
newCursorPos = lineCharEnd;
|
|
}
|
|
drgui_text_engine_move_cursor_to_character(pTB->pTL, newCursorPos);
|
|
}
|
|
if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); }
|
|
|
|
|
|
// The scroll positions may have moved so we'll need to restore them.
|
|
drgui_sb_scroll_to(pTB->pHorzScrollbar, originalScrollPosX);
|
|
drgui_sb_scroll_to(pTB->pVertScrollbar, originalScrollPosY);
|
|
|
|
return wasTextChanged;
|
|
}
|
|
|
|
|
|
void drgui_textbox_show_line_numbers(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_show(pTB->pLineNumbers);
|
|
drgui_textbox__refresh_line_numbers(pTBElement);
|
|
}
|
|
|
|
void drgui_textbox_hide_line_numbers(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_hide(pTB->pLineNumbers);
|
|
drgui_textbox__refresh_line_numbers(pTBElement);
|
|
}
|
|
|
|
|
|
void drgui_textbox_disable_vertical_scrollbar(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTB->isVertScrollbarEnabled) {
|
|
pTB->isVertScrollbarEnabled = false;
|
|
drgui_textbox__refresh_scrollbars(pTBElement);
|
|
}
|
|
}
|
|
|
|
void drgui_textbox_enable_vertical_scrollbar(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (!pTB->isVertScrollbarEnabled) {
|
|
pTB->isVertScrollbarEnabled = true;
|
|
drgui_textbox__refresh_scrollbars(pTBElement);
|
|
}
|
|
}
|
|
|
|
void drgui_textbox_disable_horizontal_scrollbar(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTB->isHorzScrollbarEnabled) {
|
|
pTB->isHorzScrollbarEnabled = false;
|
|
drgui_textbox__refresh_scrollbars(pTBElement);
|
|
}
|
|
}
|
|
|
|
void drgui_textbox_enable_horizontal_scrollbar(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (!pTB->isHorzScrollbarEnabled) {
|
|
pTB->isHorzScrollbarEnabled = true;
|
|
drgui_textbox__refresh_scrollbars(pTBElement);
|
|
}
|
|
}
|
|
|
|
drgui_element* drgui_textbox_get_vertical_scrollbar(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTB->pVertScrollbar;
|
|
}
|
|
|
|
drgui_element* drgui_textbox_get_horizontal_scrollbar(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTB->pHorzScrollbar;
|
|
}
|
|
|
|
void drgui_textbox_set_scrollbar_size(drgui_element* pTBElement, float size)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->horzScrollbarSize = size;
|
|
pTB->vertScrollbarSize = size;
|
|
|
|
drgui_textbox__refresh_scrollbars(pTBElement);
|
|
}
|
|
|
|
|
|
void drgui_textbox_set_on_cursor_move(drgui_element* pTBElement, drgui_textbox_on_cursor_move_proc proc)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->onCursorMove = proc;
|
|
}
|
|
|
|
void drgui_textbox_set_on_undo_point_changed(drgui_element* pTBElement, drgui_textbox_on_undo_point_changed_proc proc)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTB->onUndoPointChanged = proc;
|
|
}
|
|
|
|
|
|
void drgui_textbox_on_size(drgui_element* pTBElement, float newWidth, float newHeight)
|
|
{
|
|
(void)newWidth;
|
|
(void)newHeight;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
// The text engine needs to be resized.
|
|
float containerWidth;
|
|
float containerHeight;
|
|
drgui_textbox__calculate_text_engine_container_size(pTBElement, &containerWidth, &containerHeight);
|
|
drgui_text_engine_set_container_size(pTB->pTL, containerWidth, containerHeight);
|
|
|
|
// Scrollbars need to be refreshed first.
|
|
drgui_textbox__refresh_scrollbars(pTBElement);
|
|
|
|
// Line numbers need to be refreshed.
|
|
drgui_textbox__refresh_line_numbers(pTBElement);
|
|
}
|
|
|
|
void drgui_textbox_on_mouse_move(drgui_element* pTBElement, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)stateFlags;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (drgui_get_element_with_mouse_capture(pTBElement->pContext) == pTBElement)
|
|
{
|
|
float offsetX;
|
|
float offsetY;
|
|
drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY);
|
|
|
|
drgui_text_engine_move_cursor_to_point(pTB->pTL, (float)relativeMousePosX - offsetX, (float)relativeMousePosY - offsetY);
|
|
}
|
|
}
|
|
|
|
void drgui_textbox_on_mouse_button_down(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Focus the text editor.
|
|
drgui_capture_keyboard(pTBElement);
|
|
|
|
if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT)
|
|
{
|
|
// If we are not in selection mode, make sure everything is deselected.
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) == 0) {
|
|
drgui_text_engine_deselect_all(pTB->pTL);
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
} else {
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
}
|
|
|
|
float offsetX;
|
|
float offsetY;
|
|
drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY);
|
|
drgui_text_engine_move_cursor_to_point(pTB->pTL, (float)relativeMousePosX - offsetX, (float)relativeMousePosY - offsetY);
|
|
|
|
// In order to support selection with the mouse we need to capture the mouse and enter selection mode.
|
|
drgui_capture_mouse(pTBElement);
|
|
|
|
// If we didn't previously enter selection mode we'll need to do that now so we can drag select.
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) == 0) {
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
}
|
|
}
|
|
|
|
if (mouseButton == DRGUI_MOUSE_BUTTON_RIGHT)
|
|
{
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
}
|
|
}
|
|
|
|
void drgui_textbox_on_mouse_button_up(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)relativeMousePosX;
|
|
(void)relativeMousePosY;
|
|
(void)stateFlags;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT)
|
|
{
|
|
if (drgui_get_element_with_mouse_capture(pTBElement->pContext) == pTBElement)
|
|
{
|
|
// Releasing the mouse will leave selectionmode.
|
|
drgui_release_mouse(pTBElement->pContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_textbox_on_mouse_button_dblclick(drgui_element* pTBElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)mouseButton;
|
|
(void)relativeMousePosX;
|
|
(void)relativeMousePosY;
|
|
(void)stateFlags;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
void drgui_textbox_on_mouse_wheel(drgui_element* pTBElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)relativeMousePosX;
|
|
(void)relativeMousePosY;
|
|
(void)stateFlags;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_sb_scroll(pTB->pVertScrollbar, -delta * drgui_sb_get_mouse_wheel_scale(pTB->pVertScrollbar));
|
|
}
|
|
|
|
void drgui_textbox_on_key_down(drgui_element* pTBElement, drgui_key key, int stateFlags)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
switch (key)
|
|
{
|
|
case DRGUI_BACKSPACE:
|
|
{
|
|
bool wasTextChanged = false;
|
|
drgui_text_engine_prepare_undo_point(pTB->pTL);
|
|
{
|
|
if (drgui_text_engine_is_anything_selected(pTB->pTL)) {
|
|
wasTextChanged = drgui_text_engine_delete_selected_text(pTB->pTL);
|
|
} else {
|
|
wasTextChanged = drgui_text_engine_delete_character_to_left_of_cursor(pTB->pTL);
|
|
}
|
|
}
|
|
if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); }
|
|
} break;
|
|
|
|
case DRGUI_DELETE:
|
|
{
|
|
bool wasTextChanged = false;
|
|
drgui_text_engine_prepare_undo_point(pTB->pTL);
|
|
{
|
|
if (drgui_text_engine_is_anything_selected(pTB->pTL)) {
|
|
wasTextChanged = drgui_text_engine_delete_selected_text(pTB->pTL);
|
|
} else {
|
|
wasTextChanged = drgui_text_engine_delete_character_to_right_of_cursor(pTB->pTL);
|
|
}
|
|
}
|
|
if (wasTextChanged) { drgui_text_engine_commit_undo_point(pTB->pTL); }
|
|
} break;
|
|
|
|
|
|
case DRGUI_ARROW_LEFT:
|
|
{
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
}
|
|
|
|
if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) {
|
|
drgui_text_engine_move_cursor_to_start_of_selection(pTB->pTL);
|
|
drgui_text_engine_deselect_all(pTB->pTL);
|
|
} else {
|
|
drgui_text_engine_move_cursor_left(pTB->pTL);
|
|
}
|
|
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
}
|
|
} break;
|
|
|
|
case DRGUI_ARROW_RIGHT:
|
|
{
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
}
|
|
|
|
if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) {
|
|
drgui_text_engine_move_cursor_to_end_of_selection(pTB->pTL);
|
|
drgui_text_engine_deselect_all(pTB->pTL);
|
|
} else {
|
|
drgui_text_engine_move_cursor_right(pTB->pTL);
|
|
}
|
|
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
}
|
|
} break;
|
|
|
|
case DRGUI_ARROW_UP:
|
|
{
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
}
|
|
|
|
if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) {
|
|
drgui_text_engine_deselect_all(pTB->pTL);
|
|
}
|
|
|
|
drgui_text_engine_move_cursor_up(pTB->pTL);
|
|
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
}
|
|
} break;
|
|
|
|
case DRGUI_ARROW_DOWN:
|
|
{
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
}
|
|
|
|
if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) {
|
|
drgui_text_engine_deselect_all(pTB->pTL);
|
|
}
|
|
|
|
drgui_text_engine_move_cursor_down(pTB->pTL);
|
|
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
}
|
|
} break;
|
|
|
|
|
|
case DRGUI_END:
|
|
{
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
}
|
|
|
|
if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) {
|
|
drgui_text_engine_deselect_all(pTB->pTL);
|
|
}
|
|
|
|
if ((stateFlags & DRGUI_KEY_STATE_CTRL_DOWN) != 0) {
|
|
drgui_text_engine_move_cursor_to_end_of_text(pTB->pTL);
|
|
} else {
|
|
drgui_text_engine_move_cursor_to_end_of_line(pTB->pTL);
|
|
}
|
|
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
}
|
|
} break;
|
|
|
|
case DRGUI_HOME:
|
|
{
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
}
|
|
|
|
if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) {
|
|
drgui_text_engine_deselect_all(pTB->pTL);
|
|
}
|
|
|
|
if ((stateFlags & DRGUI_KEY_STATE_CTRL_DOWN) != 0) {
|
|
drgui_text_engine_move_cursor_to_start_of_text(pTB->pTL);
|
|
} else {
|
|
drgui_text_engine_move_cursor_to_start_of_line(pTB->pTL);
|
|
}
|
|
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
}
|
|
} break;
|
|
|
|
case DRGUI_PAGE_UP:
|
|
{
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
}
|
|
|
|
if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) {
|
|
drgui_text_engine_deselect_all(pTB->pTL);
|
|
}
|
|
|
|
int scrollOffset = drgui_sb_get_page_size(pTB->pVertScrollbar);
|
|
if ((stateFlags & DRGUI_KEY_STATE_CTRL_DOWN) == 0) {
|
|
drgui_sb_scroll(pTB->pVertScrollbar, -scrollOffset);
|
|
}
|
|
|
|
drgui_text_engine_move_cursor_y(pTB->pTL, -drgui_sb_get_page_size(pTB->pVertScrollbar));
|
|
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
}
|
|
} break;
|
|
|
|
case DRGUI_PAGE_DOWN:
|
|
{
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
}
|
|
|
|
if (drgui_text_engine_is_anything_selected(pTB->pTL) && !drgui_text_engine_is_in_selection_mode(pTB->pTL)) {
|
|
drgui_text_engine_deselect_all(pTB->pTL);
|
|
}
|
|
|
|
int scrollOffset = drgui_sb_get_page_size(pTB->pVertScrollbar);
|
|
if (scrollOffset > (int)(drgui_text_engine_get_line_count(pTB->pTL) - drgui_text_engine_get_cursor_line(pTB->pTL))) {
|
|
scrollOffset = 0;
|
|
}
|
|
|
|
if ((stateFlags & DRGUI_KEY_STATE_CTRL_DOWN) == 0) {
|
|
drgui_sb_scroll(pTB->pVertScrollbar, scrollOffset);
|
|
}
|
|
|
|
drgui_text_engine_move_cursor_y(pTB->pTL, drgui_sb_get_page_size(pTB->pVertScrollbar));
|
|
|
|
if ((stateFlags & DRGUI_KEY_STATE_SHIFT_DOWN) != 0) {
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
}
|
|
} break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void drgui_textbox_on_key_up(drgui_element* pTBElement, drgui_key key, int stateFlags)
|
|
{
|
|
(void)pTBElement;
|
|
(void)key;
|
|
(void)stateFlags;
|
|
}
|
|
|
|
void drgui_textbox_on_printable_key_down(drgui_element* pTBElement, unsigned int utf32, int stateFlags)
|
|
{
|
|
(void)stateFlags;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_prepare_undo_point(pTB->pTL);
|
|
{
|
|
if (drgui_text_engine_is_anything_selected(pTB->pTL)) {
|
|
drgui_text_engine_delete_selected_text(pTB->pTL);
|
|
}
|
|
|
|
drgui_text_engine_insert_character_at_cursor(pTB->pTL, utf32);
|
|
}
|
|
drgui_text_engine_commit_undo_point(pTB->pTL);
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_paint_rect(drgui_text_engine* pTL, drgui_rect rect, drgui_color color, drgui_element* pTBElement, void* pPaintData)
|
|
{
|
|
(void)pTL;
|
|
|
|
float offsetX;
|
|
float offsetY;
|
|
drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY);
|
|
|
|
drgui_draw_rect(pTBElement, drgui_offset_rect(rect, offsetX, offsetY), color, pPaintData);
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_paint_text(drgui_text_engine* pTL, drgui_text_run* pRun, drgui_element* pTBElement, void* pPaintData)
|
|
{
|
|
(void)pTL;
|
|
|
|
float offsetX;
|
|
float offsetY;
|
|
drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY);
|
|
|
|
drgui_draw_text(pTBElement, pRun->pFont, pRun->text, (int)pRun->textLength, (float)pRun->posX + offsetX, (float)pRun->posY + offsetY, pRun->textColor, pRun->backgroundColor, pPaintData);
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_dirty(drgui_text_engine* pTL, drgui_rect rect)
|
|
{
|
|
drgui_element* pTBElement = *(drgui_element**)drgui_text_engine_get_extra_data(pTL);
|
|
if (pTBElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
float offsetX;
|
|
float offsetY;
|
|
drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY);
|
|
|
|
drgui_dirty(pTBElement, drgui_offset_rect(rect, offsetX, offsetY));
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_cursor_move(drgui_text_engine* pTL)
|
|
{
|
|
// If the cursor is off the edge of the container we want to scroll it into position.
|
|
drgui_element* pTBElement = *(drgui_element**)drgui_text_engine_get_extra_data(pTL);
|
|
if (pTBElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
// If the cursor is above or below the container, we need to scroll vertically.
|
|
int iLine = (int)drgui_text_engine_get_cursor_line(pTB->pTL);
|
|
if (iLine < drgui_sb_get_scroll_position(pTB->pVertScrollbar)) {
|
|
drgui_sb_scroll_to(pTB->pVertScrollbar, iLine);
|
|
}
|
|
|
|
int iBottomLine = drgui_sb_get_scroll_position(pTB->pVertScrollbar) + drgui_sb_get_page_size(pTB->pVertScrollbar) - 1;
|
|
if (iLine >= iBottomLine) {
|
|
drgui_sb_scroll_to(pTB->pVertScrollbar, iLine - (drgui_sb_get_page_size(pTB->pVertScrollbar) - 1) + 1);
|
|
}
|
|
|
|
|
|
// If the cursor is to the left or right of the container we need to scroll horizontally.
|
|
float cursorPosX;
|
|
float cursorPosY;
|
|
drgui_text_engine_get_cursor_position(pTB->pTL, &cursorPosX, &cursorPosY);
|
|
|
|
if (cursorPosX < 0) {
|
|
drgui_sb_scroll_to(pTB->pHorzScrollbar, (int)(cursorPosX - drgui_text_engine_get_inner_offset_x(pTB->pTL)) - 100);
|
|
}
|
|
if (cursorPosX >= drgui_text_engine_get_container_width(pTB->pTL)) {
|
|
drgui_sb_scroll_to(pTB->pHorzScrollbar, (int)(cursorPosX - drgui_text_engine_get_inner_offset_x(pTB->pTL) - drgui_text_engine_get_container_width(pTB->pTL)) + 100);
|
|
}
|
|
|
|
|
|
if (pTB->onCursorMove) {
|
|
pTB->onCursorMove(pTBElement);
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_text_changed(drgui_text_engine* pTL)
|
|
{
|
|
drgui_element* pTBElement = *(drgui_element**)drgui_text_engine_get_extra_data(pTL);
|
|
if (pTBElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Scrollbars need to be refreshed whenever text is changed.
|
|
drgui_textbox__refresh_scrollbars(pTBElement);
|
|
|
|
// The line numbers need to be redrawn.
|
|
// TODO: This can probably be optimized a bit so that it is only redrawn if a line was inserted or deleted.
|
|
drgui_dirty(pTB->pLineNumbers, drgui_get_local_rect(pTB->pLineNumbers));
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_text_engine_undo_point_changed(drgui_text_engine* pTL, unsigned int iUndoPoint)
|
|
{
|
|
drgui_element* pTBElement = *(drgui_element**)drgui_text_engine_get_extra_data(pTL);
|
|
if (pTBElement == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTB->onUndoPointChanged) {
|
|
pTB->onUndoPointChanged(pTBElement, iUndoPoint);
|
|
}
|
|
}
|
|
|
|
|
|
void drgui_textbox_on_paint(drgui_element* pTBElement, drgui_rect relativeRect, void* pPaintData)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_rect textRect = drgui_textbox__get_text_rect(pTBElement);
|
|
|
|
// The dead space between the scrollbars should always be drawn with the default background color.
|
|
drgui_draw_rect(pTBElement, drgui_textbox__get_scrollbar_dead_space_rect(pTBElement), drgui_text_engine_get_default_bg_color(pTB->pTL), pPaintData);
|
|
|
|
// Border.
|
|
drgui_rect borderRect = drgui_get_local_rect(pTBElement);
|
|
drgui_draw_rect_outline(pTBElement, borderRect, pTB->borderColor, pTB->borderWidth, pPaintData);
|
|
|
|
// Padding.
|
|
drgui_rect paddingRect = drgui_grow_rect(textRect, pTB->padding);
|
|
drgui_draw_rect_outline(pTBElement, paddingRect, drgui_text_engine_get_default_bg_color(pTB->pTL), pTB->padding, pPaintData);
|
|
|
|
// Text.
|
|
drgui_set_clip(pTBElement, drgui_clamp_rect(textRect, relativeRect), pPaintData);
|
|
drgui_text_engine_paint(pTB->pTL, drgui_offset_rect(drgui_clamp_rect(textRect, relativeRect), -textRect.left, -textRect.top), pTBElement, pPaintData);
|
|
}
|
|
|
|
void drgui_textbox_on_capture_keyboard(drgui_element* pTBElement, drgui_element* pPrevCapturedElement)
|
|
{
|
|
(void)pPrevCapturedElement;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_show_cursor(pTB->pTL);
|
|
}
|
|
|
|
void drgui_textbox_on_release_keyboard(drgui_element* pTBElement, drgui_element* pNewCapturedElement)
|
|
{
|
|
(void)pNewCapturedElement;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_hide_cursor(pTB->pTL);
|
|
}
|
|
|
|
void drgui_textbox_on_capture_mouse(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
void drgui_textbox_on_release_mouse(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
}
|
|
|
|
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__get_text_offset(drgui_element* pTBElement, float* pOffsetXOut, float* pOffsetYOut)
|
|
{
|
|
float offsetX = 0;
|
|
float offsetY = 0;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB != NULL)
|
|
{
|
|
float lineNumbersWidth = 0;
|
|
if (drgui_is_visible(pTB->pLineNumbers)) {
|
|
lineNumbersWidth = drgui_get_width(pTB->pLineNumbers);
|
|
}
|
|
|
|
offsetX = pTB->borderWidth + pTB->padding + lineNumbersWidth;
|
|
offsetY = pTB->borderWidth + pTB->padding;
|
|
}
|
|
|
|
|
|
if (pOffsetXOut != NULL) {
|
|
*pOffsetXOut = offsetX;
|
|
}
|
|
if (pOffsetYOut != NULL) {
|
|
*pOffsetYOut = offsetY;
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__calculate_text_engine_container_size(drgui_element* pTBElement, float* pWidthOut, float* pHeightOut)
|
|
{
|
|
float width = 0;
|
|
float height = 0;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB != NULL)
|
|
{
|
|
float horzScrollbarSize = 0;
|
|
if (drgui_is_visible(pTB->pHorzScrollbar)) {
|
|
horzScrollbarSize = drgui_get_height(pTB->pHorzScrollbar);
|
|
}
|
|
|
|
float vertScrollbarSize = 0;
|
|
if (drgui_is_visible(pTB->pVertScrollbar)) {
|
|
vertScrollbarSize = drgui_get_width(pTB->pVertScrollbar);
|
|
}
|
|
|
|
float lineNumbersWidth = 0;
|
|
if (drgui_is_visible(pTB->pLineNumbers)) {
|
|
lineNumbersWidth = drgui_get_width(pTB->pLineNumbers);
|
|
}
|
|
|
|
width = drgui_get_width(pTBElement) - (pTB->borderWidth + pTB->padding)*2 - vertScrollbarSize - lineNumbersWidth;
|
|
height = drgui_get_height(pTBElement) - (pTB->borderWidth + pTB->padding)*2 - horzScrollbarSize;
|
|
}
|
|
|
|
if (pWidthOut != NULL) {
|
|
*pWidthOut = width;
|
|
}
|
|
if (pHeightOut != NULL) {
|
|
*pHeightOut = height;
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE drgui_rect drgui_textbox__get_text_rect(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
if (pTB == NULL) {
|
|
return drgui_make_rect(0, 0, 0, 0);
|
|
}
|
|
|
|
float offsetX;
|
|
float offsetY;
|
|
drgui_textbox__get_text_offset(pTBElement, &offsetX, &offsetY);
|
|
|
|
float width;
|
|
float height;
|
|
drgui_textbox__calculate_text_engine_container_size(pTBElement, &width, &height);
|
|
|
|
return drgui_make_rect(offsetX, offsetY, offsetX + width, offsetY + height);
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__refresh_scrollbars(drgui_element* pTBElement)
|
|
{
|
|
// The layout depends on the range because we may be dynamically hiding and showing the scrollbars depending on the range. Thus, we
|
|
// refresh the range first. However, dynamically showing and hiding the scrollbars (which is done when the layout is refreshed) affects
|
|
// the size of the text box, which in turn affects the range. Thus, we need to refresh the ranges a second time after the layouts.
|
|
|
|
drgui_textbox__refresh_scrollbar_ranges(pTBElement);
|
|
drgui_textbox__refresh_scrollbar_layouts(pTBElement);
|
|
drgui_textbox__refresh_scrollbar_ranges(pTBElement);
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__refresh_scrollbar_ranges(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
// The vertical scrollbar is based on the line count.
|
|
size_t lineCount = drgui_text_engine_get_line_count(pTB->pTL);
|
|
size_t pageSize = drgui_text_engine_get_visible_line_count_starting_at(pTB->pTL, drgui_sb_get_scroll_position(pTB->pVertScrollbar));
|
|
drgui_sb_set_range_and_page_size(pTB->pVertScrollbar, 0, (int)(lineCount + pageSize - 1 - 1), (int)pageSize); // -1 to make the range 0 based. -1 to ensure at least one line is visible.
|
|
|
|
if (drgui_sb_is_thumb_visible(pTB->pVertScrollbar)) {
|
|
if (!drgui_is_visible(pTB->pVertScrollbar)) {
|
|
drgui_show(pTB->pVertScrollbar);
|
|
}
|
|
} else {
|
|
if (drgui_is_visible(pTB->pVertScrollbar)) {
|
|
drgui_hide(pTB->pVertScrollbar);
|
|
}
|
|
}
|
|
|
|
|
|
// The horizontal scrollbar is a per-pixel scrollbar, and is based on the width of the text versus the width of the container.
|
|
drgui_rect textRect = drgui_text_engine_get_text_rect_relative_to_bounds(pTB->pTL);
|
|
float containerWidth;
|
|
drgui_text_engine_get_container_size(pTB->pTL, &containerWidth, NULL);
|
|
drgui_sb_set_range_and_page_size(pTB->pHorzScrollbar, 0, (int)(textRect.right - textRect.left + (containerWidth/2)), (int)containerWidth);
|
|
|
|
if (drgui_sb_is_thumb_visible(pTB->pHorzScrollbar)) {
|
|
if (!drgui_is_visible(pTB->pHorzScrollbar)) {
|
|
drgui_show(pTB->pHorzScrollbar);
|
|
drgui_textbox__refresh_line_numbers(pTBElement);
|
|
}
|
|
} else {
|
|
if (drgui_is_visible(pTB->pHorzScrollbar)) {
|
|
drgui_hide(pTB->pHorzScrollbar);
|
|
drgui_textbox__refresh_line_numbers(pTBElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__refresh_scrollbar_layouts(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
float offsetLeft = pTB->borderWidth;
|
|
float offsetTop = pTB->borderWidth;
|
|
float offsetRight = pTB->borderWidth;
|
|
float offsetBottom = pTB->borderWidth;
|
|
|
|
float scrollbarSizeH = (drgui_sb_is_thumb_visible(pTB->pHorzScrollbar) && pTB->isHorzScrollbarEnabled) ? pTB->horzScrollbarSize : 0;
|
|
float scrollbarSizeV = (drgui_sb_is_thumb_visible(pTB->pVertScrollbar) && pTB->isVertScrollbarEnabled) ? pTB->vertScrollbarSize : 0;
|
|
|
|
drgui_set_size(pTB->pVertScrollbar, scrollbarSizeV, drgui_get_height(pTBElement) - scrollbarSizeH - (offsetTop + offsetBottom));
|
|
drgui_set_size(pTB->pHorzScrollbar, drgui_get_width(pTBElement) - scrollbarSizeV - (offsetLeft + offsetRight), scrollbarSizeH);
|
|
|
|
drgui_set_relative_position(pTB->pVertScrollbar, drgui_get_width(pTBElement) - scrollbarSizeV - offsetRight, offsetTop);
|
|
drgui_set_relative_position(pTB->pHorzScrollbar, offsetLeft, drgui_get_height(pTBElement) - scrollbarSizeH - offsetBottom);
|
|
|
|
|
|
// A change in the layout of the horizontal scrollbar will affect the layout of the line numbers.
|
|
drgui_textbox__refresh_line_numbers(pTBElement);
|
|
}
|
|
|
|
DRGUI_PRIVATE drgui_rect drgui_textbox__get_scrollbar_dead_space_rect(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
float offsetLeft = pTB->borderWidth;
|
|
float offsetTop = pTB->borderWidth;
|
|
float offsetRight = pTB->borderWidth;
|
|
float offsetBottom = pTB->borderWidth;
|
|
|
|
float scrollbarSizeH = (drgui_is_visible(pTB->pHorzScrollbar) && pTB->isHorzScrollbarEnabled) ? drgui_get_width(pTB->pHorzScrollbar) : 0;
|
|
float scrollbarSizeV = (drgui_is_visible(pTB->pVertScrollbar) && pTB->isHorzScrollbarEnabled) ? drgui_get_height(pTB->pVertScrollbar) : 0;
|
|
|
|
if (scrollbarSizeH == 0 && scrollbarSizeV == 0) {
|
|
return drgui_make_rect(0, 0, 0, 0);
|
|
}
|
|
|
|
return drgui_make_rect(scrollbarSizeH + offsetLeft, scrollbarSizeV + offsetTop, drgui_get_width(pTBElement) - offsetRight, drgui_get_height(pTBElement) - offsetBottom);
|
|
}
|
|
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_mouse_move_line_numbers(drgui_element* pLineNumbers, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)relativeMousePosX;
|
|
|
|
drgui_element* pTBElement = *(drgui_element**)drgui_get_extra_data(pLineNumbers);
|
|
assert(pTBElement != NULL);
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
if ((stateFlags & DRGUI_MOUSE_BUTTON_LEFT_DOWN) != 0)
|
|
{
|
|
if (drgui_get_element_with_mouse_capture(pLineNumbers->pContext) == pLineNumbers)
|
|
{
|
|
// We just move the cursor around based on the line number we've moved over.
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
{
|
|
//float offsetX = pTextEditorData->padding;
|
|
float offsetY = pTB->padding;
|
|
size_t iLine = drgui_text_engine_get_line_at_pos_y(pTB->pTL, relativeMousePosY - offsetY);
|
|
size_t iAnchorLine = pTB->iLineSelectAnchor;
|
|
size_t lineCount = drgui_text_engine_get_line_count(pTB->pTL);
|
|
|
|
size_t iSelectionFirstLine = drgui_text_engine_get_selection_first_line(pTB->pTL);
|
|
size_t iSelectionLastLine = drgui_text_engine_get_selection_last_line(pTB->pTL);
|
|
if (iSelectionLastLine != iSelectionFirstLine) {
|
|
iSelectionLastLine -= 1;
|
|
}
|
|
|
|
// If we're moving updwards we want to position the cursor at the start of the line. Otherwise we want to move the cursor to the start
|
|
// of the next line, or the end of the text.
|
|
bool movingUp = false;
|
|
if (iLine < iAnchorLine) {
|
|
movingUp = true;
|
|
}
|
|
|
|
// If we're moving up the selection anchor needs to be placed at the end of the last line. Otherwise we need to move it to the start
|
|
// of the first line.
|
|
if (movingUp) {
|
|
if (iAnchorLine + 1 < lineCount) {
|
|
drgui_text_engine_move_selection_anchor_to_start_of_line(pTB->pTL, iAnchorLine + 1);
|
|
} else {
|
|
drgui_text_engine_move_selection_anchor_to_end_of_line(pTB->pTL, iAnchorLine);
|
|
}
|
|
} else {
|
|
drgui_text_engine_move_selection_anchor_to_start_of_line(pTB->pTL, iAnchorLine);
|
|
}
|
|
|
|
|
|
// If we're moving up we want the cursor to be placed at the start of the selection range. Otherwise we want to place the cursor
|
|
// at the end of the selection range.
|
|
if (movingUp) {
|
|
drgui_text_engine_move_cursor_to_start_of_line_by_index(pTB->pTL, iLine);
|
|
} else {
|
|
if (iLine + 1 < lineCount) {
|
|
drgui_text_engine_move_cursor_to_start_of_line_by_index(pTB->pTL, iLine + 1);
|
|
} else {
|
|
drgui_text_engine_move_cursor_to_end_of_line_by_index(pTB->pTL, iLine);
|
|
}
|
|
}
|
|
}
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
}
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_mouse_button_down_line_numbers(drgui_element* pLineNumbers, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)relativeMousePosX;
|
|
(void)stateFlags;
|
|
|
|
drgui_element* pTBElement = *(drgui_element**)drgui_get_extra_data(pLineNumbers);
|
|
assert(pTBElement != NULL);
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT)
|
|
{
|
|
//float offsetX = pTextEditorData->padding;
|
|
float offsetY = pTB->padding;
|
|
pTB->iLineSelectAnchor = drgui_text_engine_get_line_at_pos_y(pTB->pTL, relativeMousePosY - offsetY);
|
|
|
|
drgui_text_engine_deselect_all(pTB->pTL);
|
|
|
|
drgui_text_engine_move_cursor_to_start_of_line_by_index(pTB->pTL, pTB->iLineSelectAnchor);
|
|
|
|
drgui_text_engine_enter_selection_mode(pTB->pTL);
|
|
{
|
|
if (pTB->iLineSelectAnchor + 1 < drgui_text_engine_get_line_count(pTB->pTL)) {
|
|
drgui_text_engine_move_cursor_to_start_of_line_by_index(pTB->pTL, pTB->iLineSelectAnchor + 1);
|
|
} else {
|
|
drgui_text_engine_move_cursor_to_end_of_line(pTB->pTL);
|
|
}
|
|
}
|
|
drgui_text_engine_leave_selection_mode(pTB->pTL);
|
|
|
|
drgui_capture_mouse(pLineNumbers);
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_mouse_button_up_line_numbers(drgui_element* pLineNumbers, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)relativeMousePosX;
|
|
(void)relativeMousePosY;
|
|
(void)stateFlags;
|
|
|
|
if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT) {
|
|
if (drgui_has_mouse_capture(pLineNumbers)) {
|
|
drgui_release_mouse(pLineNumbers->pContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_paint_rect_line_numbers(drgui_text_engine* pLayout, drgui_rect rect, drgui_color color, drgui_element* pTBElement, void* pPaintData)
|
|
{
|
|
(void)pLayout;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
float offsetX = pTB->padding;
|
|
float offsetY = pTB->padding;
|
|
|
|
drgui_draw_rect(pTB->pLineNumbers, drgui_offset_rect(rect, offsetX, offsetY), color, pPaintData);
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_paint_text_line_numbers(drgui_text_engine* pLayout, drgui_text_run* pRun, drgui_element* pTBElement, void* pPaintData)
|
|
{
|
|
(void)pLayout;
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
float offsetX = pTB->padding;
|
|
float offsetY = pTB->padding;
|
|
drgui_draw_text(pTB->pLineNumbers, pRun->pFont, pRun->text, (int)pRun->textLength, (float)pRun->posX + offsetX, (float)pRun->posY + offsetY, pRun->textColor, pRun->backgroundColor, pPaintData);
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__on_paint_line_numbers(drgui_element* pLineNumbers, drgui_rect relativeRect, void* pPaintData)
|
|
{
|
|
(void)relativeRect;
|
|
|
|
drgui_element* pTBElement = *((drgui_element**)drgui_get_extra_data(pLineNumbers));
|
|
assert(pTBElement != NULL);
|
|
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
float lineNumbersWidth = drgui_get_width(pLineNumbers) - (pTB->padding*2) - pTB->lineNumbersPaddingRight;
|
|
float lineNumbersHeight = drgui_get_height(pLineNumbers) - (pTB->padding*2);
|
|
|
|
drgui_text_engine_paint_line_numbers(pTB->pTL, lineNumbersWidth, lineNumbersHeight, pTB->lineNumbersColor, pTB->lineNumbersBackgroundColor, drgui_textbox__on_paint_text_line_numbers, drgui_textbox__on_paint_rect_line_numbers, pTBElement, pPaintData);
|
|
|
|
drgui_draw_rect_outline(pLineNumbers, drgui_get_local_rect(pLineNumbers), pTB->lineNumbersBackgroundColor, pTB->padding, pPaintData);
|
|
|
|
// Right padding.
|
|
drgui_rect rightPaddingRect = drgui_get_local_rect(pLineNumbers);
|
|
rightPaddingRect.right -= pTB->padding;
|
|
rightPaddingRect.left = rightPaddingRect.right - pTB->lineNumbersPaddingRight;
|
|
drgui_draw_rect(pLineNumbers, rightPaddingRect, pTB->lineNumbersBackgroundColor, pPaintData);
|
|
}
|
|
|
|
DRGUI_PRIVATE void drgui_textbox__refresh_line_numbers(drgui_element* pTBElement)
|
|
{
|
|
drgui_textbox* pTB = (drgui_textbox*)drgui_get_extra_data(pTBElement);
|
|
assert(pTB != NULL);
|
|
|
|
float lineNumbersWidth = 0;
|
|
if (drgui_is_visible(pTB->pLineNumbers)) {
|
|
lineNumbersWidth = pTB->lineNumbersWidth;
|
|
}
|
|
|
|
float scrollbarHeight = drgui_is_visible(pTB->pHorzScrollbar) ? drgui_get_height(pTB->pHorzScrollbar) : 0;
|
|
drgui_set_size(pTB->pLineNumbers, lineNumbersWidth, drgui_get_height(pTBElement) - scrollbarHeight);
|
|
|
|
|
|
// The size of the text container may have changed.
|
|
float textEditorWidth;
|
|
float textEditorHeight;
|
|
drgui_textbox__calculate_text_engine_container_size(pTBElement, &textEditorWidth, &textEditorHeight);
|
|
drgui_text_engine_set_container_size(pTB->pTL, textEditorWidth, textEditorHeight);
|
|
|
|
|
|
// Force a redraw just to be sure everything is in a valid state.
|
|
drgui_dirty(pTBElement, drgui_get_local_rect(pTBElement));
|
|
}
|
|
#endif //DR_GUI_IMPLEMENTATION
|
|
#endif //DRGUI_NO_TEXT_EDITING
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Tree View
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// QUICK NOTES
|
|
//
|
|
// Tree-View Controls
|
|
// - A tree-view control is a complex control with a hierarchy of items. They are typically used for file explorers.
|
|
|
|
#ifndef drgui_tree_view_h
|
|
#define drgui_tree_view_h
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define EG_MAX_TREE_VIEW_ITEM_TEXT_LENGTH 256
|
|
|
|
typedef struct drgui_tree_view_item drgui_tree_view_item;
|
|
|
|
typedef void (* drgui_tvi_on_mouse_move_proc) (drgui_tree_view_item* pItem, int relativeMousePosX, int relativeMousePosY, bool* pIsOverArrow);
|
|
typedef void (* drgui_tvi_on_mouse_leave_proc) (drgui_tree_view_item* pItem);
|
|
typedef void (* drgui_tvi_on_paint_proc) (drgui_element* pTVElement, drgui_tree_view_item* pItem, drgui_rect relativeClippingRect, drgui_color backgroundColor, float offsetX, float offsetY, float width, float height, void* pPaintData);
|
|
typedef void (* drgui_tvi_measure_proc) (drgui_tree_view_item* pItem, float* pWidthOut, float* pHeightOut);
|
|
typedef void (* drgui_tvi_on_picked_proc) (drgui_tree_view_item* pItem);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Tree-View
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Creates a tree-view control.
|
|
drgui_element* drgui_create_tree_view(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData);
|
|
|
|
/// Deletes the given tree-view control.
|
|
void drgui_delete_tree_view(drgui_element* pTVElement);
|
|
|
|
|
|
/// Retrieves the size of the extra data associated with the given tree-view control.
|
|
size_t drgui_tv_get_extra_data_size(drgui_element* pTVElement);
|
|
|
|
/// Retrieves a pointer to the buffer containing the given tree-view's extra data.
|
|
void* drgui_tv_get_extra_data(drgui_element* pTVElement);
|
|
|
|
/// Retrieves a pointer to the root element of the given tree view control.
|
|
drgui_tree_view_item* drgui_tv_get_root_item(drgui_element* pTVElement);
|
|
|
|
/// Retrieves a pointer to the vertical scrollbar.
|
|
drgui_element* drgui_tv_get_vertical_scrollbar(drgui_element* pTVElement);
|
|
|
|
/// Retrieves a pointer to the horizontal scrollbar.
|
|
drgui_element* drgui_tv_get_horizontal_scrollbar(drgui_element* pTVElement);
|
|
|
|
|
|
/// Sets the default background color.
|
|
void drgui_tv_set_default_background_color(drgui_element* pTVElement, drgui_color color);
|
|
|
|
/// Retrieves the default background color.
|
|
drgui_color drgui_tv_get_default_background_color(drgui_element* pTVElement);
|
|
|
|
/// Sets the default background color of hovered items.
|
|
void drgui_tv_set_hovered_background_color(drgui_element* pTVElement, drgui_color color);
|
|
|
|
/// Retrieves the default background color of hovered items.
|
|
drgui_color drgui_tv_get_hovered_background_color(drgui_element* pTVElement);
|
|
|
|
/// Sets the default background color of selected items.
|
|
void drgui_tv_set_selected_background_color(drgui_element* pTVElement, drgui_color color);
|
|
|
|
/// Retrieves the default background color of selected items.
|
|
drgui_color drgui_tv_get_selected_background_color(drgui_element* pTVElement);
|
|
|
|
/// Sets the amount of indentation to apply to each child item in the given tree-view.
|
|
void drgui_tv_set_child_offset_x(drgui_element* pTVElement, float childOffsetX);
|
|
|
|
/// Retrieves the amount of indentation to apply to each child item in the given tree-view.
|
|
float drgui_tv_get_child_offset_x(drgui_element* pTVElement);
|
|
|
|
|
|
/// Measures the given item.
|
|
bool drgui_tv_measure_item(drgui_element* pTVElement, drgui_tree_view_item* pItem, float* pWidthOut, float* pHeightOut);
|
|
|
|
/// Deselects every tree-view item.
|
|
void drgui_tv_deselect_all_items(drgui_element* pTVElement);
|
|
|
|
/// Enables multi-select.
|
|
///
|
|
/// @remarks
|
|
/// While this is enabled, selections will accumulate. Typically you would call this when the user hits
|
|
/// the CTRL key, and then call drgui_tv_disable_multi_select() when the user releases it.
|
|
void drgui_tv_enable_multi_select(drgui_element* pTVElement);
|
|
|
|
/// Disables multi-select.
|
|
void drgui_tv_disable_multi_select(drgui_element* pTVElement);
|
|
|
|
/// Determines whether or not multi-select is enabled.
|
|
bool drgui_tv_is_multi_select_enabled(drgui_element* pTVElement);
|
|
|
|
|
|
/// Retrieves the first selected item.
|
|
///
|
|
/// @remarks
|
|
/// This runs in linear time.
|
|
drgui_tree_view_item* drgui_tv_get_first_selected_item(drgui_element* pTVElement);
|
|
|
|
/// Retrieves the next select item, not including the given item.
|
|
///
|
|
/// @remarks
|
|
/// Use this in conjunction with drgui_tv_get_first_selected_item() to iterate over each selected item.
|
|
/// @par
|
|
/// The order in which retrieving selected items is based on their location in the hierarchy, and not the
|
|
/// order in which they were selected.
|
|
drgui_tree_view_item* drgui_tv_get_next_selected_item(drgui_element* pTVElement, drgui_tree_view_item* pItem);
|
|
|
|
|
|
/// Sets the function to call when the mouse is moved while over a tree-view item.
|
|
void drgui_tv_set_on_item_mouse_move(drgui_element* pTVElement, drgui_tvi_on_mouse_move_proc proc);
|
|
|
|
/// Sets the function call when the mouse leaves a tree-view item.
|
|
void drgui_tv_set_on_item_mouse_leave(drgui_element* pTVElement, drgui_tvi_on_mouse_leave_proc proc);
|
|
|
|
/// Sets the function to call when a tree-view item needs to be drawn.
|
|
void drgui_tv_set_on_item_paint(drgui_element* pTVElement, drgui_tvi_on_paint_proc proc);
|
|
|
|
/// Sets the function to call when a tree-view item needs to be measured.
|
|
void drgui_tv_set_on_item_measure(drgui_element* pTVElement, drgui_tvi_measure_proc proc);
|
|
|
|
/// Sets the function to call when a tree-view item is picked.
|
|
///
|
|
/// @remarks
|
|
/// An item is "picked" when it is a leaf item (has no children) and is double-clicked.
|
|
void drgui_tv_set_on_item_picked(drgui_element* pTVElement, drgui_tvi_on_picked_proc proc);
|
|
|
|
|
|
/// Called when the size event needs to be processed for the given tree-view control.
|
|
void drgui_tv_on_size(drgui_element* pTVElement, float newWidth, float newHeight);
|
|
|
|
/// Called when the mouse leave event needs to be processed for the given tree-view control.
|
|
void drgui_tv_on_mouse_leave(drgui_element* pTVElement);
|
|
|
|
/// Called when the mouse move event needs to be processed for the given tree-view control.
|
|
void drgui_tv_on_mouse_move(drgui_element* pTVElement, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the mouse button down event needs to be processed for the given tree-view control.
|
|
void drgui_tv_on_mouse_button_down(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the mouse button up event needs to be processed for the given tree-view control.
|
|
void drgui_tv_on_mouse_button_up(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the mouse button double-click event needs to be processed for the given tree-view control.
|
|
void drgui_tv_on_mouse_button_dblclick(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the mouse wheel event needs to be processed for the given tree-view control.
|
|
void drgui_tv_on_mouse_wheel(drgui_element* pTVElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags);
|
|
|
|
/// Called when the paint event needs to be processed for the given tree-view control.
|
|
void drgui_tv_on_paint(drgui_element* pTVElement, drgui_rect relativeClippingRect, void* pPaintData);
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Tree-View Item
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Creates a tree view item.
|
|
///
|
|
/// @remarks
|
|
/// When pParent is non-null, the tree-view control must match that of the tree-view control that owns the
|
|
/// parent item.
|
|
drgui_tree_view_item* drgui_tv_create_item(drgui_element* pTVElement, drgui_tree_view_item* pParent, size_t extraDataSize, const void* pExtraData);
|
|
|
|
/// Recursively deletes a tree view item.
|
|
void drgui_tvi_delete(drgui_tree_view_item* pItem);
|
|
|
|
/// Retrieves the tree-view GUI element that owns the given item.
|
|
drgui_element* drgui_tvi_get_tree_view_element(drgui_tree_view_item* pItem);
|
|
|
|
/// Retrieves the size of the extra data associated with the given tree-view item.
|
|
size_t drgui_tvi_get_extra_data_size(drgui_tree_view_item* pItem);
|
|
|
|
/// Retrieves a pointer to the extra data associated with the given tree-view item.
|
|
void* drgui_tvi_get_extra_data(drgui_tree_view_item* pItem);
|
|
|
|
|
|
/// Retrieves the parent tree-view item.
|
|
drgui_tree_view_item* drgui_tvi_get_parent(drgui_tree_view_item* pItem);
|
|
|
|
/// Retrieves a pointer to the first child of the given tree-view item.
|
|
drgui_tree_view_item* drgui_tvi_get_first_child(drgui_tree_view_item* pItem);
|
|
|
|
/// Retrieves a pointer to the last child of the given tree-view item.
|
|
drgui_tree_view_item* drgui_tvi_get_last_child(drgui_tree_view_item* pItem);
|
|
|
|
/// Retrieves a pointer to the next sibling of the given tree-view item.
|
|
drgui_tree_view_item* drgui_tvi_get_next_sibling(drgui_tree_view_item* pItem);
|
|
|
|
/// Retrieves a pointer to the previous sibling of the given tree-view item.
|
|
drgui_tree_view_item* drgui_tvi_get_prev_sibling(drgui_tree_view_item* pItem);
|
|
|
|
/// Appends a tree view item as a child of the given parent item.
|
|
void drgui_tvi_append(drgui_tree_view_item* pItem, drgui_tree_view_item* pParent);
|
|
|
|
/// Prepends a tree view item as a child of the given parent item.
|
|
void drgui_tvi_prepend(drgui_tree_view_item* pItem, drgui_tree_view_item* pParent);
|
|
|
|
/// Appends the given tree view item to the given sibling.
|
|
void drgui_tvi_append_sibling(drgui_tree_view_item* pItemToAppend, drgui_tree_view_item* pItemToAppendTo);
|
|
|
|
/// Prepends the given tree view item to the given sibling.
|
|
void drgui_tvi_prepend_sibling(drgui_tree_view_item* pItemToPrepend, drgui_tree_view_item* pItemToPrependTo);
|
|
|
|
|
|
/// Determines whether or not the given item has any children.
|
|
bool drgui_tvi_has_children(drgui_tree_view_item* pItem);
|
|
|
|
/// Retrieves the depth of the item.
|
|
///
|
|
/// @remarks
|
|
/// This is a recursive call and runs in linear time.
|
|
int drgui_tvi_get_depth(drgui_tree_view_item* pItem);
|
|
|
|
/// Retrieves a pointer to the next visible item in the hierarchy that is not a child.
|
|
///
|
|
/// @remarks
|
|
/// This is used for iterating.
|
|
/// @par
|
|
/// <pDepthInOut> is an input and output parameter that is decremented whenver the next item is an ancestor.
|
|
drgui_tree_view_item* drgui_tvi_next_visible_non_child(drgui_tree_view_item* pItem, int* pDepthInOut);
|
|
|
|
|
|
/// Selects the given item.
|
|
void drgui_tvi_select(drgui_tree_view_item* pItem);
|
|
|
|
/// Deselects the given item.
|
|
void drgui_tvi_deselect(drgui_tree_view_item* pItem);
|
|
|
|
/// Determines whether or not the given tree view item is selected.
|
|
bool drgui_tvi_is_selected(drgui_tree_view_item* pItem);
|
|
|
|
/// Expands the given item.
|
|
void drgui_tvi_expand(drgui_tree_view_item* pItem);
|
|
|
|
/// Collapses the given item.
|
|
void drgui_tvi_collapse(drgui_tree_view_item* pItem);
|
|
|
|
/// Determines whether or not the given item is expanded.
|
|
bool drgui_tvi_is_expanded(drgui_tree_view_item* pItem);
|
|
|
|
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif //drgui_tree_view_h
|
|
|
|
|
|
#ifdef DR_GUI_IMPLEMENTATION
|
|
typedef struct drgui_tree_view drgui_tree_view;
|
|
|
|
struct drgui_tree_view
|
|
{
|
|
/// The root tree-view item.
|
|
drgui_tree_view_item* pRootItem;
|
|
|
|
/// The vertical scrollbar.
|
|
drgui_element* pScrollbarV;
|
|
|
|
/// The horizontal scrollbar.
|
|
drgui_element* pScrollbarH;
|
|
|
|
|
|
/// The default background color.
|
|
drgui_color defaultBGColor;
|
|
|
|
/// The hovered background color.
|
|
drgui_color hoveredBGColor;
|
|
|
|
/// The selected background color.
|
|
drgui_color selectedBGColor;
|
|
|
|
/// The amount of indentation to apply to each child item.
|
|
float childOffsetX;
|
|
|
|
|
|
/// The function to call when an item needs to handle a mouse movement event.
|
|
drgui_tvi_on_mouse_move_proc onItemMouseMove;
|
|
|
|
/// The function to call when an item needs to handle a mouse leave event.
|
|
drgui_tvi_on_mouse_leave_proc onItemMouseLeave;
|
|
|
|
/// The function to call when an item needs to be drawn.
|
|
drgui_tvi_on_paint_proc onItemPaint;
|
|
|
|
/// The function to call when an item needs to be measured.
|
|
drgui_tvi_measure_proc onItemMeasure;
|
|
|
|
/// The function to call when an item is picked.
|
|
drgui_tvi_on_picked_proc onItemPicked;
|
|
|
|
|
|
/// A pointer to the item the mouse is current hovered over.
|
|
drgui_tree_view_item* pHoveredItem;
|
|
|
|
/// Whether or not the mouse is hovered over the arrow of pHoveredItem.
|
|
bool isMouseOverArrow;
|
|
|
|
/// Whether or not the mouse is over the given element.
|
|
bool isMouseOver;
|
|
|
|
/// The relative position of the mouse on the x axis. This is updated whenever the mouse_move event is received.
|
|
int relativeMousePosX;
|
|
|
|
/// The relative position of the mouse on the y axis. This is updated whenever the mouse_move event is received.
|
|
int relativeMousePosY;
|
|
|
|
|
|
/// Whether or not multi-select is enabled.
|
|
bool isMultiSelectEnabled;
|
|
|
|
/// Whether or not range-select is enabled.
|
|
bool isRangeSelectEnabled;
|
|
|
|
|
|
/// The size of the extra data.
|
|
size_t extraDataSize;
|
|
|
|
/// A pointer to the extra data buffer.
|
|
char pExtraData[1];
|
|
};
|
|
|
|
struct drgui_tree_view_item
|
|
{
|
|
/// The tree-view control that owns this item.
|
|
drgui_element* pTVElement;
|
|
|
|
|
|
/// A pointer to the parent item.
|
|
drgui_tree_view_item* pParent;
|
|
|
|
/// A pointer to the first child.
|
|
drgui_tree_view_item* pFirstChild;
|
|
|
|
/// A pointer to the last child.
|
|
drgui_tree_view_item* pLastChild;
|
|
|
|
/// A pointer to the next sibling.
|
|
drgui_tree_view_item* pNextSibling;
|
|
|
|
/// A pointer to the prev sibling.
|
|
drgui_tree_view_item* pPrevSibling;
|
|
|
|
|
|
/// Whether or not the item is select.
|
|
bool isSelected;
|
|
|
|
/// Whether or not the item is expanded.
|
|
bool isExpanded;
|
|
|
|
|
|
/// The size of the extra data.
|
|
size_t extraDataSize;
|
|
|
|
/// A pointer to the extra data buffer.
|
|
char pExtraData[1];
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
/// A pointer to the relevant item.
|
|
drgui_tree_view_item* pItem;
|
|
|
|
/// The width of the item.
|
|
float width;
|
|
|
|
/// The height of the item.
|
|
float height;
|
|
|
|
/// The position of the item on the x axis.
|
|
float posX;
|
|
|
|
/// Top position of the item on the y axis.
|
|
float posY;
|
|
|
|
/// The depth of the item. This is used to calculate the offset of the item.
|
|
int depth;
|
|
|
|
} drgui_tree_view_iterator;
|
|
|
|
typedef struct
|
|
{
|
|
/// The width of the item.
|
|
float width;
|
|
|
|
/// The height of the item.
|
|
float height;
|
|
|
|
/// The position of the item on the x axis.
|
|
float posX;
|
|
|
|
/// Top position of the item on the y axis.
|
|
float posY;
|
|
|
|
} drgui_tree_view_item_metrics;
|
|
|
|
typedef struct
|
|
{
|
|
/// A pointer to the tree-view control that owns the scrollbar.
|
|
drgui_element* pTVElement;
|
|
|
|
} drgui_tree_view_scrollbar_data;
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Tree-View
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Refreshes the layout of the given tree-view control and schedules a redraw.
|
|
static void drgui_tv_refresh_and_redraw(drgui_element* pTVElement);
|
|
|
|
/// Repositions and resizes the scrollbars of the given tree-view control.
|
|
static void drgui_tv_refresh_scrollbar_layouts(drgui_element* pTVElement);
|
|
|
|
/// Refreshes the ranges and page sizes of the scrollbars of the given tree-view control.
|
|
static void drgui_tv_refresh_scrollbar_ranges(drgui_element* pTVElement);
|
|
|
|
/// Retrieves the rectangle of the little space that sits between the two scrollbars.
|
|
static drgui_rect drgui_tv_get_scrollbar_dead_space_rect(drgui_element* pTVElement);
|
|
|
|
/// Retrieves the rectangle region that does not include the scrollbars. This rectangle is used for clipping when drawing the tree-view.
|
|
static drgui_rect drgui_tv_get_inner_rect(drgui_element* pTVElement);
|
|
|
|
/// Paints the items of the given tree-view control.
|
|
static void drgui_tv_paint_items(drgui_element* pTVElement, drgui_rect relativeClippingRect, void* pPaintData, float* pItemsBottomOut);
|
|
|
|
/// Creates an iterator beginning at the given item.
|
|
static bool drgui_tv_begin_at(drgui_tree_view_item* pFirst, drgui_tree_view_iterator* pIteratorOut);
|
|
|
|
/// Moves to the next item in the iterator.
|
|
static bool drgui_tv_next_visible(drgui_tree_view_iterator* pIterator);
|
|
|
|
/// Paints the given item.
|
|
static void drgui_tv_paint_item(drgui_element* pTVElement, drgui_tree_view_item* pItem, drgui_rect relativeClippingRect, float posX, float posY, float width, float height, void* pPaintData);
|
|
|
|
/// Finds the item under the given point.
|
|
static drgui_tree_view_item* drgui_tv_find_item_under_point(drgui_element* pTV, float relativePosX, float relativePosY, drgui_tree_view_item_metrics* pMetricsOut);
|
|
|
|
/// Recursively deselects every item, including the given one.
|
|
static void drgui_tv_deselect_all_items_recursive(drgui_tree_view_item* pItem);
|
|
|
|
/// Called when the mouse enters a scrollbar. We use this to ensure there are no items marked as hovered as the use moves the
|
|
/// mouse from the tree-view to the scrollbars.
|
|
static void drgui_tv_on_mouse_enter_scrollbar(drgui_element* pSBElement);
|
|
|
|
/// Called when the vertical scrollbar is scrolled.
|
|
static void drgui_tv_on_scroll_v(drgui_element* pSBElement, int scrollPos);
|
|
|
|
/// Called when the horizontal scrollbar is scrolled.
|
|
static void drgui_tv_on_scroll_h(drgui_element* pSBElement, int scrollPos);
|
|
|
|
/// Retrieves a pointer to the first visible item on the page, based on the scroll position.
|
|
static drgui_tree_view_item* drgui_tv_find_first_visible_item_on_page(drgui_element* pTVElement);
|
|
|
|
|
|
drgui_element* drgui_create_tree_view(drgui_context* pContext, drgui_element* pParent, size_t extraDataSize, const void* pExtraData)
|
|
{
|
|
drgui_element* pTVElement = drgui_create_element(pContext, pParent, sizeof(drgui_tree_view) + extraDataSize, NULL);
|
|
if (pTVElement == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
pTV->pRootItem = drgui_tv_create_item(pTVElement, NULL, 0, NULL);
|
|
if (pTV->pRootItem == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
|
|
drgui_tree_view_scrollbar_data sbdata;
|
|
sbdata.pTVElement = pTVElement;
|
|
|
|
pTV->pScrollbarV = drgui_create_scrollbar(pContext, pTVElement, drgui_sb_orientation_vertical, sizeof(sbdata), &sbdata);
|
|
drgui_set_on_mouse_enter(pTV->pScrollbarV, drgui_tv_on_mouse_enter_scrollbar);
|
|
drgui_sb_set_on_scroll(pTV->pScrollbarV, drgui_tv_on_scroll_v);
|
|
|
|
pTV->pScrollbarH = drgui_create_scrollbar(pContext, pTVElement, drgui_sb_orientation_horizontal, sizeof(sbdata), &sbdata);
|
|
drgui_set_on_mouse_enter(pTV->pScrollbarH, drgui_tv_on_mouse_enter_scrollbar);
|
|
drgui_sb_set_on_scroll(pTV->pScrollbarH, drgui_tv_on_scroll_h);
|
|
|
|
|
|
pTV->defaultBGColor = drgui_rgb(96, 96, 96);
|
|
pTV->hoveredBGColor = drgui_rgb(112, 112, 112);
|
|
pTV->selectedBGColor = drgui_rgb(80, 160, 255);
|
|
pTV->childOffsetX = 16;
|
|
|
|
pTV->onItemMouseMove = NULL;
|
|
pTV->onItemMouseLeave = NULL;
|
|
pTV->onItemPaint = NULL;
|
|
pTV->onItemMeasure = NULL;
|
|
pTV->onItemPicked = NULL;
|
|
|
|
pTV->pHoveredItem = NULL;
|
|
pTV->isMouseOverArrow = false;
|
|
pTV->isMouseOver = false;
|
|
pTV->relativeMousePosX = 0;
|
|
pTV->relativeMousePosY = 0;
|
|
|
|
pTV->isMultiSelectEnabled = false;
|
|
pTV->isRangeSelectEnabled = false;
|
|
|
|
pTV->extraDataSize = extraDataSize;
|
|
if (pExtraData != NULL) {
|
|
memcpy(pTV->pExtraData, pExtraData, extraDataSize);
|
|
}
|
|
|
|
|
|
// Default event handlers.
|
|
drgui_set_on_size(pTVElement, drgui_tv_on_size);
|
|
drgui_set_on_mouse_leave(pTVElement, drgui_tv_on_mouse_leave);
|
|
drgui_set_on_mouse_move(pTVElement, drgui_tv_on_mouse_move);
|
|
drgui_set_on_mouse_button_down(pTVElement, drgui_tv_on_mouse_button_down);
|
|
drgui_set_on_mouse_button_up(pTVElement, drgui_tv_on_mouse_button_up);
|
|
drgui_set_on_mouse_button_dblclick(pTVElement, drgui_tv_on_mouse_button_dblclick);
|
|
drgui_set_on_mouse_wheel(pTVElement, drgui_tv_on_mouse_wheel);
|
|
drgui_set_on_paint(pTVElement, drgui_tv_on_paint);
|
|
|
|
|
|
// Set the mouse wheel scale to 3 by default for the vertical scrollbar.
|
|
drgui_sb_set_mouse_wheel_scele(pTV->pScrollbarV, 3);
|
|
|
|
|
|
return pTVElement;
|
|
}
|
|
|
|
void drgui_delete_tree_view(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Recursively delete the tree view items.
|
|
drgui_tvi_delete(pTV->pRootItem);
|
|
|
|
// Delete the element last.
|
|
drgui_delete_element(pTVElement);
|
|
}
|
|
|
|
|
|
size_t drgui_tv_get_extra_data_size(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTV->extraDataSize;
|
|
}
|
|
|
|
void* drgui_tv_get_extra_data(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTV->pExtraData;
|
|
}
|
|
|
|
drgui_tree_view_item* drgui_tv_get_root_item(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTV->pRootItem;
|
|
}
|
|
|
|
drgui_element* drgui_tv_get_vertical_scrollbar(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTV->pScrollbarV;
|
|
}
|
|
|
|
drgui_element* drgui_tv_get_horizontal_scrollbar(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pTV->pScrollbarH;
|
|
}
|
|
|
|
|
|
void drgui_tv_set_default_background_color(drgui_element* pTVElement, drgui_color color)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->defaultBGColor = color;
|
|
}
|
|
|
|
drgui_color drgui_tv_get_default_background_color(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTV->defaultBGColor;
|
|
}
|
|
|
|
void drgui_tv_set_hovered_background_color(drgui_element* pTVElement, drgui_color color)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->hoveredBGColor = color;
|
|
}
|
|
|
|
drgui_color drgui_tv_get_hovered_background_color(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTV->hoveredBGColor;
|
|
}
|
|
|
|
void drgui_tv_set_selected_background_color(drgui_element* pTVElement, drgui_color color)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->selectedBGColor = color;
|
|
}
|
|
|
|
drgui_color drgui_tv_get_selected_background_color(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return drgui_rgb(0, 0, 0);
|
|
}
|
|
|
|
return pTV->selectedBGColor;
|
|
}
|
|
|
|
void drgui_tv_set_child_offset_x(drgui_element* pTVElement, float childOffsetX)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->childOffsetX = childOffsetX;
|
|
}
|
|
|
|
float drgui_tv_get_child_offset_x(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pTV->childOffsetX;
|
|
}
|
|
|
|
|
|
bool drgui_tv_measure_item(drgui_element* pTVElement, drgui_tree_view_item* pItem, float* pWidthOut, float* pHeightOut)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (pItem == NULL || pItem->pTVElement != pTVElement) {
|
|
return false;
|
|
}
|
|
|
|
if (pTV->onItemMeasure)
|
|
{
|
|
pTV->onItemMeasure(pItem, pWidthOut, pHeightOut);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void drgui_tv_deselect_all_items(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tv_deselect_all_items_recursive(pTV->pRootItem);
|
|
|
|
// TODO: Only redraw the region that actually changed.
|
|
drgui_dirty(pTVElement, drgui_get_local_rect(pTVElement));
|
|
}
|
|
|
|
|
|
void drgui_tv_enable_multi_select(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->isMultiSelectEnabled = true;
|
|
}
|
|
|
|
void drgui_tv_disable_multi_select(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->isMultiSelectEnabled = false;
|
|
}
|
|
|
|
bool drgui_tv_is_multi_select_enabled(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pTV->isMultiSelectEnabled;
|
|
}
|
|
|
|
drgui_tree_view_item* drgui_tv_get_first_selected_item(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_tree_view_iterator i;
|
|
if (drgui_tv_begin_at(pTV->pRootItem->pFirstChild, &i))
|
|
{
|
|
do
|
|
{
|
|
if (drgui_tvi_is_selected(i.pItem)) {
|
|
return i.pItem;
|
|
}
|
|
|
|
} while (drgui_tv_next_visible(&i));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
drgui_tree_view_item* drgui_tv_get_next_selected_item(drgui_element* pTVElement, drgui_tree_view_item* pItem)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
drgui_tree_view_iterator i;
|
|
if (drgui_tv_begin_at(pItem, &i))
|
|
{
|
|
// Note that we're not including <pItem> in this iteration.
|
|
while (drgui_tv_next_visible(&i))
|
|
{
|
|
if (drgui_tvi_is_selected(i.pItem)) {
|
|
return i.pItem;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void drgui_tv_set_on_item_mouse_move(drgui_element* pTVElement, drgui_tvi_on_mouse_move_proc proc)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->onItemMouseMove = proc;
|
|
}
|
|
|
|
void drgui_tv_set_on_item_mouse_leave(drgui_element* pTVElement, drgui_tvi_on_mouse_leave_proc proc)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->onItemMouseLeave = proc;
|
|
}
|
|
|
|
void drgui_tv_set_on_item_paint(drgui_element* pTVElement, drgui_tvi_on_paint_proc proc)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->onItemPaint = proc;
|
|
}
|
|
|
|
void drgui_tv_set_on_item_measure(drgui_element* pTVElement, drgui_tvi_measure_proc proc)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->onItemMeasure = proc;
|
|
}
|
|
|
|
void drgui_tv_set_on_item_picked(drgui_element* pTVElement, drgui_tvi_on_picked_proc proc)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->onItemPicked = proc;
|
|
}
|
|
|
|
|
|
void drgui_tv_on_size(drgui_element* pTVElement, float newWidth, float newHeight)
|
|
{
|
|
(void)newWidth;
|
|
(void)newHeight;
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Move the scrollbars.
|
|
drgui_tv_refresh_scrollbar_layouts(pTVElement);
|
|
|
|
// Refresh the scrollbar ranges.
|
|
drgui_tv_refresh_scrollbar_ranges(pTVElement);
|
|
}
|
|
|
|
void drgui_tv_on_mouse_leave(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->isMouseOver = false;
|
|
|
|
if (pTV->pHoveredItem != NULL || pTV->isMouseOverArrow)
|
|
{
|
|
if (pTV->onItemMouseLeave) {
|
|
pTV->onItemMouseLeave(pTV->pHoveredItem);
|
|
}
|
|
|
|
pTV->pHoveredItem = NULL;
|
|
pTV->isMouseOverArrow = false;
|
|
|
|
// For now just redraw the entire control, but should optimize this to only redraw the regions of the new and old hovered items.
|
|
drgui_dirty(pTVElement, drgui_get_local_rect(pTVElement));
|
|
}
|
|
}
|
|
|
|
void drgui_tv_on_mouse_move(drgui_element* pTVElement, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)stateFlags;
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
pTV->isMouseOver = true;
|
|
pTV->relativeMousePosX = relativeMousePosX;
|
|
pTV->relativeMousePosY = relativeMousePosY;
|
|
|
|
// If the mouse has entered into the dead space between the scrollbars, we just pretend the mouse has left the tree-view
|
|
// control entirely by posting a manual on_mouse_leave event and returning straight away.
|
|
if (drgui_rect_contains_point(drgui_tv_get_scrollbar_dead_space_rect(pTVElement), (float)relativeMousePosX, (float)relativeMousePosY)) {
|
|
drgui_tv_on_mouse_leave(pTVElement);
|
|
return;
|
|
}
|
|
|
|
|
|
drgui_tree_view_item_metrics newHoveredItemMetrics;
|
|
drgui_tree_view_item* pNewHoveredItem = drgui_tv_find_item_under_point(pTVElement, (float)relativeMousePosX, (float)relativeMousePosY, &newHoveredItemMetrics);
|
|
drgui_tree_view_item* pOldHoveredItem = pTV->pHoveredItem;
|
|
|
|
bool wasMouseOverArrow = pTV->isMouseOverArrow;
|
|
pTV->isMouseOverArrow = false;
|
|
|
|
if (pNewHoveredItem != NULL)
|
|
{
|
|
if (pTV->onItemMouseMove)
|
|
{
|
|
float relativeMousePosXToItem = (float)relativeMousePosX - newHoveredItemMetrics.posX + drgui_sb_get_scroll_position(pTV->pScrollbarH);
|
|
float relativeMousePosYToItem = (float)relativeMousePosY - newHoveredItemMetrics.posY;
|
|
|
|
if (relativeMousePosXToItem >= 0 && relativeMousePosXToItem < newHoveredItemMetrics.width &&
|
|
relativeMousePosYToItem >= 0 && relativeMousePosYToItem < newHoveredItemMetrics.height)
|
|
{
|
|
pTV->onItemMouseMove(pNewHoveredItem, (int)relativeMousePosXToItem, (int)relativeMousePosYToItem, &pTV->isMouseOverArrow);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pNewHoveredItem != pOldHoveredItem || wasMouseOverArrow != pTV->isMouseOverArrow)
|
|
{
|
|
if (pNewHoveredItem != pOldHoveredItem && pOldHoveredItem != NULL)
|
|
{
|
|
if (pTV->onItemMouseLeave) {
|
|
pTV->onItemMouseLeave(pOldHoveredItem);
|
|
}
|
|
}
|
|
|
|
|
|
pTV->pHoveredItem = pNewHoveredItem;
|
|
|
|
// TODO: Optimize this so that only the rectangle region encompassing the two relevant items is marked as dirty.
|
|
drgui_dirty(pTVElement, drgui_get_local_rect(pTVElement));
|
|
}
|
|
}
|
|
|
|
void drgui_tv_on_mouse_button_down(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)relativeMousePosX;
|
|
(void)relativeMousePosY;
|
|
(void)stateFlags;
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT)
|
|
{
|
|
if (pTV->isMouseOverArrow)
|
|
{
|
|
if (drgui_tvi_is_expanded(pTV->pHoveredItem)) {
|
|
drgui_tvi_collapse(pTV->pHoveredItem);
|
|
} else {
|
|
drgui_tvi_expand(pTV->pHoveredItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pTV->isMultiSelectEnabled)
|
|
{
|
|
if (drgui_tvi_is_selected(pTV->pHoveredItem)) {
|
|
drgui_tvi_deselect(pTV->pHoveredItem);
|
|
} else {
|
|
drgui_tvi_select(pTV->pHoveredItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: Check if range selection is enabled and handle it here.
|
|
|
|
drgui_tv_deselect_all_items(pTVElement);
|
|
drgui_tvi_select(pTV->pHoveredItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_tv_on_mouse_button_up(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)mouseButton;
|
|
(void)relativeMousePosX;
|
|
(void)relativeMousePosY;
|
|
(void)stateFlags;
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
// TODO: Implement me.
|
|
}
|
|
|
|
void drgui_tv_on_mouse_button_dblclick(drgui_element* pTVElement, int mouseButton, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)relativeMousePosX;
|
|
(void)relativeMousePosY;
|
|
(void)stateFlags;
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (mouseButton == DRGUI_MOUSE_BUTTON_LEFT)
|
|
{
|
|
if (!pTV->isMouseOverArrow)
|
|
{
|
|
if (drgui_tvi_has_children(pTV->pHoveredItem))
|
|
{
|
|
// It is a parent item, so toggle it.
|
|
if (drgui_tvi_is_expanded(pTV->pHoveredItem)) {
|
|
drgui_tvi_collapse(pTV->pHoveredItem);
|
|
} else {
|
|
drgui_tvi_expand(pTV->pHoveredItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// It is a leaf item, so pick it.
|
|
if (pTV->onItemPicked) {
|
|
pTV->onItemPicked(pTV->pHoveredItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void drgui_tv_on_mouse_wheel(drgui_element* pTVElement, int delta, int relativeMousePosX, int relativeMousePosY, int stateFlags)
|
|
{
|
|
(void)relativeMousePosX;
|
|
(void)relativeMousePosY;
|
|
(void)stateFlags;
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_sb_scroll(pTV->pScrollbarV, -delta * drgui_sb_get_mouse_wheel_scale(pTV->pScrollbarV));
|
|
}
|
|
|
|
void drgui_tv_on_paint(drgui_element* pTVElement, drgui_rect relativeClippingRect, void* pPaintData)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
// The dead space between the scrollbars should always be drawn with the default background color.
|
|
drgui_draw_rect(pTVElement, drgui_tv_get_scrollbar_dead_space_rect(pTVElement), pTV->defaultBGColor, pPaintData);
|
|
|
|
// The clipping rectangle needs to be clamped to the local rectangle that is shrunk such that it does not
|
|
// include the scrollbars. If we don't do this we'll end up drawing underneath the scrollbars which will
|
|
// cause flickering.
|
|
drgui_rect innerClippingRect = drgui_clamp_rect(drgui_tv_get_inner_rect(pTVElement), relativeClippingRect);
|
|
drgui_set_clip(pTVElement, innerClippingRect, pPaintData);
|
|
|
|
|
|
// The main content of the tree-view is drawn in two parts. The first part (the top part) contains all of
|
|
// the tree-view items. The second part (the bottom part) is just the background region that is not covered
|
|
// by items.
|
|
|
|
// We draw the tree-view items (the top part) first. This will retrieve the position of the bottom of the
|
|
// items which is used to determine how much empty space is remaining below it so we can draw a quad over
|
|
// that part.
|
|
float itemsBottom = 0;
|
|
drgui_tv_paint_items(pTVElement, innerClippingRect, pPaintData, &itemsBottom);
|
|
|
|
|
|
// At this point the items have been drawn. All that remains is the part of the background that is not
|
|
// covered by items. We can determine this by looking at <itemsBottom>.
|
|
if (itemsBottom < relativeClippingRect.bottom && itemsBottom < drgui_get_relative_position_y(pTV->pScrollbarH))
|
|
{
|
|
drgui_draw_rect(pTVElement, drgui_make_rect(0, itemsBottom, drgui_get_relative_position_x(pTV->pScrollbarV), drgui_get_relative_position_y(pTV->pScrollbarH)), pTV->defaultBGColor, pPaintData);
|
|
}
|
|
}
|
|
|
|
|
|
static void drgui_tv_refresh_and_redraw(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
// Refresh scrollbar ranges and page sizes.
|
|
drgui_tv_refresh_scrollbar_ranges(pTVElement);
|
|
|
|
// For now, just redraw the entire control.
|
|
drgui_dirty(pTVElement, drgui_get_local_rect(pTVElement));
|
|
}
|
|
|
|
static void drgui_tv_refresh_scrollbar_layouts(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
// Vertical scrollbar.
|
|
drgui_set_size(pTV->pScrollbarV, 16, drgui_get_height(pTVElement) - 16);
|
|
drgui_set_relative_position(pTV->pScrollbarV, drgui_get_width(pTVElement) - drgui_get_width(pTV->pScrollbarV), 0);
|
|
|
|
// Horizontal scrollbar.
|
|
drgui_set_size(pTV->pScrollbarH, drgui_get_width(pTVElement) - 16, 16);
|
|
drgui_set_relative_position(pTV->pScrollbarH, 0, drgui_get_height(pTVElement) - drgui_get_height(pTV->pScrollbarH));
|
|
}
|
|
|
|
static void drgui_tv_refresh_scrollbar_ranges(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
float innerWidth = 0;
|
|
unsigned int totalItemCount = 0;
|
|
unsigned int pageItemCount = 0;
|
|
|
|
drgui_tree_view_iterator i;
|
|
if (drgui_tv_begin_at(pTV->pRootItem->pFirstChild, &i))
|
|
{
|
|
do
|
|
{
|
|
float itemRight = i.posX + i.width;
|
|
if (itemRight > innerWidth) {
|
|
innerWidth = itemRight;
|
|
}
|
|
|
|
float itemBottom = i.posY + i.height;
|
|
if (itemBottom > 0 && itemBottom < drgui_get_relative_position_y(pTV->pScrollbarH)) {
|
|
pageItemCount += 1;
|
|
}
|
|
|
|
totalItemCount += 1;
|
|
|
|
} while (drgui_tv_next_visible(&i));
|
|
}
|
|
|
|
if (totalItemCount == 0)
|
|
{
|
|
// Vertical.
|
|
drgui_sb_set_range(pTV->pScrollbarV, 0, 0);
|
|
drgui_sb_set_page_size(pTV->pScrollbarV, 0);
|
|
|
|
// Horizontal.
|
|
drgui_sb_set_range(pTV->pScrollbarH, 0, 0);
|
|
drgui_sb_set_page_size(pTV->pScrollbarH, 0);
|
|
}
|
|
else
|
|
{
|
|
// Vertical.
|
|
drgui_sb_set_range(pTV->pScrollbarV, 0, (int)totalItemCount - 1); // - 1 because it's a 0-based range.
|
|
drgui_sb_set_page_size(pTV->pScrollbarV, pageItemCount);
|
|
|
|
// Horizontal.
|
|
drgui_sb_set_range(pTV->pScrollbarH, 0, (int)innerWidth);
|
|
drgui_sb_set_page_size(pTV->pScrollbarH, (int)drgui_get_relative_position_x(pTV->pScrollbarV));
|
|
}
|
|
}
|
|
|
|
static drgui_rect drgui_tv_get_scrollbar_dead_space_rect(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return drgui_make_rect(0, 0, 0, 0);
|
|
}
|
|
|
|
return drgui_make_rect(drgui_get_width(pTV->pScrollbarH), drgui_get_height(pTV->pScrollbarV), drgui_get_width(pTVElement), drgui_get_height(pTVElement));
|
|
}
|
|
|
|
static drgui_rect drgui_tv_get_inner_rect(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return drgui_make_rect(0, 0, 0, 0);
|
|
}
|
|
|
|
drgui_rect result = drgui_get_local_rect(pTVElement);
|
|
result.right -= drgui_get_width(pTV->pScrollbarV);
|
|
result.bottom -= drgui_get_height(pTV->pScrollbarH);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void drgui_tv_paint_items(drgui_element* pTVElement, drgui_rect relativeClippingRect, void* pPaintData, float* pItemsBottomOut)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
float itemsBottom = 0;
|
|
|
|
// For now we will begin at the root item, but later we want to begin at the first visible item which will be based on the
|
|
// scroll position.
|
|
drgui_tree_view_iterator i;
|
|
if (drgui_tv_begin_at(drgui_tv_find_first_visible_item_on_page(pTVElement), &i))
|
|
{
|
|
do
|
|
{
|
|
drgui_tv_paint_item(pTVElement, i.pItem, relativeClippingRect, i.posX, i.posY, i.width, i.height, pPaintData);
|
|
|
|
// Restore the clipping rectangle in case the application changed the clipping rectangle.
|
|
drgui_set_clip(pTVElement, relativeClippingRect, pPaintData);
|
|
|
|
itemsBottom = i.posY + i.height;
|
|
|
|
} while (itemsBottom < relativeClippingRect.bottom && drgui_tv_next_visible(&i));
|
|
}
|
|
|
|
|
|
if (pItemsBottomOut != NULL) {
|
|
*pItemsBottomOut = itemsBottom;
|
|
}
|
|
}
|
|
|
|
static bool drgui_tv_begin_at(drgui_tree_view_item* pFirst, drgui_tree_view_iterator* pIteratorOut)
|
|
{
|
|
if (pFirst == NULL || pIteratorOut == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (!drgui_tv_measure_item(pFirst->pTVElement, pFirst, &pIteratorOut->width, &pIteratorOut->height)) {
|
|
return false;
|
|
}
|
|
|
|
const int depth = drgui_tvi_get_depth(pFirst);
|
|
|
|
pIteratorOut->pItem = pFirst;
|
|
pIteratorOut->depth = depth;
|
|
pIteratorOut->posX = depth * drgui_tv_get_child_offset_x(pFirst->pTVElement);
|
|
pIteratorOut->posY = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool drgui_tv_next_visible(drgui_tree_view_iterator* pIterator)
|
|
{
|
|
assert(pIterator != NULL);
|
|
|
|
if (pIterator->pItem == NULL) {
|
|
return false;
|
|
}
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pIterator->pItem->pTVElement);
|
|
if (pTV == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (drgui_tvi_has_children(pIterator->pItem) && drgui_tvi_is_expanded(pIterator->pItem))
|
|
{
|
|
pIterator->pItem = pIterator->pItem->pFirstChild;
|
|
pIterator->depth += 1;
|
|
}
|
|
else
|
|
{
|
|
pIterator->pItem = drgui_tvi_next_visible_non_child(pIterator->pItem, &pIterator->depth);
|
|
}
|
|
|
|
|
|
if (pIterator->pItem == NULL) {
|
|
return false;
|
|
}
|
|
|
|
pIterator->posX = pIterator->depth * drgui_tv_get_child_offset_x(pIterator->pItem->pTVElement);
|
|
pIterator->posY += pIterator->height;
|
|
|
|
if (!drgui_tv_measure_item(pIterator->pItem->pTVElement, pIterator->pItem, &pIterator->width, &pIterator->height)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void drgui_tv_paint_item(drgui_element* pTVElement, drgui_tree_view_item* pItem, drgui_rect relativeClippingRect, float posX, float posY, float width, float height, void* pPaintData)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pTV->onItemPaint)
|
|
{
|
|
// We draw an item in two main parts, with the first part being the background section to the left and right of the item and the
|
|
// second part being the item itself. The first part we do ourselves, whereas the second part we pass off to the host application.
|
|
|
|
// The background section to the left and right of the main content is done first, by us.
|
|
drgui_color bgcolor;
|
|
if (drgui_tvi_is_selected(pItem)) {
|
|
bgcolor = pTV->selectedBGColor;
|
|
} else if (pTV->pHoveredItem == pItem) {
|
|
bgcolor = pTV->hoveredBGColor;
|
|
} else {
|
|
bgcolor = pTV->defaultBGColor;
|
|
}
|
|
|
|
float innerOffsetX = (float)-drgui_sb_get_scroll_position(pTV->pScrollbarH);
|
|
|
|
// Left.
|
|
if (posX + innerOffsetX > 0) {
|
|
drgui_draw_rect(pTVElement, drgui_make_rect(0, posY, posX + innerOffsetX, posY + height), bgcolor, pPaintData);
|
|
}
|
|
|
|
// Right.
|
|
if (posX + width + innerOffsetX < drgui_get_relative_position_x(pTV->pScrollbarV)) {
|
|
drgui_draw_rect(pTVElement, drgui_make_rect(posX + width + innerOffsetX, posY, drgui_get_relative_position_x(pTV->pScrollbarV), posY + height), bgcolor, pPaintData);
|
|
}
|
|
|
|
|
|
// At this point if were to finish drawing we'd have a hole where the main content of the item should be. To fill this we need to
|
|
// let the host application do it.
|
|
pTV->onItemPaint(pTVElement, pItem, relativeClippingRect, bgcolor, posX + innerOffsetX, posY, width, height, pPaintData);
|
|
}
|
|
}
|
|
|
|
static drgui_tree_view_item* drgui_tv_find_item_under_point(drgui_element* pTVElement, float relativePosX, float relativePosY, drgui_tree_view_item_metrics* pMetricsOut)
|
|
{
|
|
(void)relativePosX; // <-- Unused because we treat items as though they are infinitely wide.
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// For now we will begin at the root item, but later we want to begin at the first visible item which will be based on the
|
|
// scroll position.
|
|
drgui_tree_view_iterator i;
|
|
if (drgui_tv_begin_at(drgui_tv_find_first_visible_item_on_page(pTVElement), &i))
|
|
{
|
|
do
|
|
{
|
|
if (relativePosY >= i.posY && relativePosY < i.posY + i.height)
|
|
{
|
|
if (pMetricsOut != NULL)
|
|
{
|
|
pMetricsOut->posX = i.posX;
|
|
pMetricsOut->posY = i.posY;
|
|
pMetricsOut->width = i.width;
|
|
pMetricsOut->height = i.height;
|
|
}
|
|
|
|
return i.pItem;
|
|
}
|
|
|
|
} while ((i.posY + i.height < drgui_get_relative_position_y(pTV->pScrollbarH)) && drgui_tv_next_visible(&i));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void drgui_tv_deselect_all_items_recursive(drgui_tree_view_item* pItem)
|
|
{
|
|
pItem->isSelected = false;
|
|
|
|
for (drgui_tree_view_item* pChild = pItem->pFirstChild; pChild != NULL; pChild = pChild->pNextSibling)
|
|
{
|
|
drgui_tv_deselect_all_items_recursive(pChild);
|
|
}
|
|
}
|
|
|
|
static void drgui_tv_on_mouse_enter_scrollbar(drgui_element* pSBElement)
|
|
{
|
|
drgui_tree_view_scrollbar_data* pSB = (drgui_tree_view_scrollbar_data*)drgui_sb_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
// We just pretend the mouse has left the tree-view entirely. This will ensure any item marked as hovered is unmarked and redrawn.
|
|
drgui_tv_on_mouse_leave(pSB->pTVElement);
|
|
}
|
|
|
|
static void drgui_tv_on_scroll_v(drgui_element* pSBElement, int scrollPos)
|
|
{
|
|
(void)scrollPos;
|
|
|
|
drgui_tree_view_scrollbar_data* pSB = (drgui_tree_view_scrollbar_data*)drgui_sb_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pSB->pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
// As we scroll, the mouse will be placed over a different item. We just post a manual mouse_move event to trigger a refresh.
|
|
if (pTV->isMouseOver) {
|
|
drgui_tv_on_mouse_move(pSB->pTVElement, pTV->relativeMousePosX, pTV->relativeMousePosY, 0);
|
|
}
|
|
|
|
// The paint routine is tied directly to the scrollbars, so all we need to do is mark it as dirty to trigger a redraw.
|
|
drgui_dirty(pSB->pTVElement, drgui_get_local_rect(pSB->pTVElement));
|
|
}
|
|
|
|
static void drgui_tv_on_scroll_h(drgui_element* pSBElement, int scrollPos)
|
|
{
|
|
(void)scrollPos;
|
|
|
|
drgui_tree_view_scrollbar_data* pSB = (drgui_tree_view_scrollbar_data*)drgui_sb_get_extra_data(pSBElement);
|
|
if (pSB == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pSB->pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
// The paint routine is tied directly to the scrollbars, so all we need to do is mark it as dirty to trigger a redraw.
|
|
drgui_dirty(pSB->pTVElement, drgui_get_local_rect(pSB->pTVElement));
|
|
}
|
|
|
|
static drgui_tree_view_item* drgui_tv_find_first_visible_item_on_page(drgui_element* pTVElement)
|
|
{
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pTVElement);
|
|
if (pTV == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// We just keep iterating until we hit the index of the scroll position.
|
|
int index = 0;
|
|
|
|
drgui_tree_view_iterator i;
|
|
if (drgui_tv_begin_at(pTV->pRootItem->pFirstChild, &i))
|
|
{
|
|
do
|
|
{
|
|
if (index == drgui_sb_get_scroll_position(pTV->pScrollbarV)) {
|
|
return i.pItem;
|
|
}
|
|
|
|
index += 1;
|
|
|
|
} while (drgui_tv_next_visible(&i));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Tree-View Item
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Detaches the given tree-view item from it's parent and siblings.
|
|
static void drgui_tvi_detach(drgui_tree_view_item* pItem);
|
|
|
|
drgui_tree_view_item* drgui_tv_create_item(drgui_element* pTVElement, drgui_tree_view_item* pParent, size_t extraDataSize, const void* pExtraData)
|
|
{
|
|
if (pTVElement == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (pParent != NULL && pParent->pTVElement != pTVElement) {
|
|
return NULL;
|
|
}
|
|
|
|
|
|
drgui_tree_view_item* pItem = (drgui_tree_view_item*)malloc(sizeof(*pItem) + extraDataSize);
|
|
if (pItem == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
pItem->pTVElement = pTVElement;
|
|
pItem->pParent = NULL;
|
|
pItem->pFirstChild = NULL;
|
|
pItem->pLastChild = NULL;
|
|
pItem->pNextSibling = NULL;
|
|
pItem->pPrevSibling = NULL;
|
|
pItem->isSelected = false;
|
|
pItem->isExpanded = false;
|
|
|
|
pItem->extraDataSize = extraDataSize;
|
|
if (pExtraData != NULL) {
|
|
memcpy(pItem->pExtraData, pExtraData, extraDataSize);
|
|
}
|
|
|
|
|
|
// Append the item to the end of the parent item.
|
|
drgui_tvi_append(pItem, pParent);
|
|
|
|
return pItem;
|
|
}
|
|
|
|
void drgui_tvi_delete(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
// Children need to be deleted first.
|
|
while (pItem->pFirstChild != NULL) {
|
|
drgui_tvi_delete(pItem->pFirstChild);
|
|
}
|
|
|
|
// We need to grab a pointer to the main tree-view control so we can refresh and redraw it after we have detached the item.
|
|
drgui_element* pTVElement = pItem->pTVElement;
|
|
|
|
// The item needs to be completely detached first.
|
|
drgui_tvi_detach(pItem);
|
|
|
|
// Refresh the layout and redraw the tree-view control.
|
|
drgui_tv_refresh_and_redraw(pTVElement);
|
|
|
|
// Free the item last for safety.
|
|
free(pItem);
|
|
}
|
|
|
|
drgui_element* drgui_tvi_get_tree_view_element(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pItem->pTVElement;
|
|
}
|
|
|
|
size_t drgui_tvi_get_extra_data_size(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
return pItem->extraDataSize;
|
|
}
|
|
|
|
void* drgui_tvi_get_extra_data(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pItem->pExtraData;
|
|
}
|
|
|
|
|
|
drgui_tree_view_item* drgui_tvi_get_parent(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pItem->pParent;
|
|
}
|
|
|
|
drgui_tree_view_item* drgui_tvi_get_first_child(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pItem->pFirstChild;
|
|
}
|
|
|
|
drgui_tree_view_item* drgui_tvi_get_last_child(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pItem->pLastChild;
|
|
}
|
|
|
|
drgui_tree_view_item* drgui_tvi_get_next_sibling(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pItem->pNextSibling;
|
|
}
|
|
|
|
drgui_tree_view_item* drgui_tvi_get_prev_sibling(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
return pItem->pPrevSibling;
|
|
}
|
|
|
|
void drgui_tvi_append(drgui_tree_view_item* pItem, drgui_tree_view_item* pParent)
|
|
{
|
|
if (pItem == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pItem->pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
// If a parent was not specified, append to the root item.
|
|
if (pParent == NULL)
|
|
{
|
|
if (pTV->pRootItem != NULL) {
|
|
drgui_tvi_append(pItem, pTV->pRootItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(pItem->pTVElement == pParent->pTVElement);
|
|
|
|
// Detach the child from it's current parent first.
|
|
drgui_tvi_detach(pItem);
|
|
|
|
pItem->pParent = pParent;
|
|
assert(pItem->pParent != NULL);
|
|
|
|
if (pItem->pParent->pLastChild != NULL) {
|
|
pItem->pPrevSibling = pItem->pParent->pLastChild;
|
|
pItem->pPrevSibling->pNextSibling = pItem;
|
|
}
|
|
|
|
if (pItem->pParent->pFirstChild == NULL) {
|
|
pItem->pParent->pFirstChild = pItem;
|
|
}
|
|
|
|
pItem->pParent->pLastChild = pItem;
|
|
|
|
|
|
// Refresh the layout and redraw the tree-view control.
|
|
drgui_tv_refresh_and_redraw(pItem->pTVElement);
|
|
}
|
|
}
|
|
|
|
void drgui_tvi_prepend(drgui_tree_view_item* pItem, drgui_tree_view_item* pParent)
|
|
{
|
|
if (pItem == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pItem->pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
// If a parent was not specified, prepend to the root item.
|
|
if (pParent == NULL)
|
|
{
|
|
if (pTV->pRootItem != NULL) {
|
|
drgui_tvi_prepend(pItem, pTV->pRootItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(pItem->pTVElement == pParent->pTVElement);
|
|
|
|
// Detach the child from it's current parent first.
|
|
drgui_tvi_detach(pItem);
|
|
|
|
pItem->pParent = pParent;
|
|
assert(pItem->pParent != NULL);
|
|
|
|
if (pItem->pParent->pFirstChild != NULL) {
|
|
pItem->pNextSibling = pItem->pParent->pFirstChild;
|
|
pItem->pNextSibling->pPrevSibling = pItem;
|
|
}
|
|
|
|
if (pItem->pParent->pLastChild == NULL) {
|
|
pItem->pParent->pLastChild = pItem;
|
|
}
|
|
|
|
pItem->pParent->pFirstChild = pItem;
|
|
|
|
|
|
// Refresh the layout and redraw the tree-view control.
|
|
drgui_tv_refresh_and_redraw(pItem->pTVElement);
|
|
}
|
|
}
|
|
|
|
void drgui_tvi_append_sibling(drgui_tree_view_item* pItemToAppend, drgui_tree_view_item* pItemToAppendTo)
|
|
{
|
|
if (pItemToAppend == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pItemToAppend->pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
// If a parent was not specified, append to the root item.
|
|
if (pItemToAppendTo == NULL)
|
|
{
|
|
if (pTV->pRootItem != NULL) {
|
|
drgui_tvi_append(pItemToAppend, pTV->pRootItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(pItemToAppend->pTVElement == pItemToAppendTo->pTVElement);
|
|
|
|
// Detach the child from it's current parent first.
|
|
drgui_tvi_detach(pItemToAppend);
|
|
|
|
|
|
pItemToAppend->pParent = pItemToAppendTo->pParent;
|
|
assert(pItemToAppend->pParent != NULL);
|
|
|
|
pItemToAppend->pNextSibling = pItemToAppendTo->pNextSibling;
|
|
pItemToAppend->pPrevSibling = pItemToAppendTo;
|
|
|
|
pItemToAppendTo->pNextSibling->pPrevSibling = pItemToAppend;
|
|
pItemToAppendTo->pNextSibling = pItemToAppend;
|
|
|
|
if (pItemToAppend->pParent->pLastChild == pItemToAppendTo) {
|
|
pItemToAppend->pParent->pLastChild = pItemToAppend;
|
|
}
|
|
|
|
|
|
// Refresh the layout and redraw the tree-view control.
|
|
drgui_tv_refresh_and_redraw(pItemToAppend->pTVElement);
|
|
}
|
|
}
|
|
|
|
void drgui_tvi_prepend_sibling(drgui_tree_view_item* pItemToPrepend, drgui_tree_view_item* pItemToPrependTo)
|
|
{
|
|
if (pItemToPrepend == NULL) {
|
|
return;
|
|
}
|
|
|
|
drgui_tree_view* pTV = (drgui_tree_view*)drgui_get_extra_data(pItemToPrepend->pTVElement);
|
|
if (pTV == NULL) {
|
|
return;
|
|
}
|
|
|
|
|
|
// If a parent was not specified, prepend to the root item.
|
|
if (pItemToPrependTo == NULL)
|
|
{
|
|
if (pTV->pRootItem != NULL) {
|
|
drgui_tvi_prepend(pItemToPrepend, pTV->pRootItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(pItemToPrepend->pTVElement == pItemToPrependTo->pTVElement);
|
|
|
|
// Detach the child from it's current parent first.
|
|
drgui_tvi_detach(pItemToPrepend);
|
|
|
|
|
|
pItemToPrepend->pParent = pItemToPrependTo->pParent;
|
|
assert(pItemToPrepend->pParent != NULL);
|
|
|
|
pItemToPrepend->pPrevSibling = pItemToPrependTo->pNextSibling;
|
|
pItemToPrepend->pNextSibling = pItemToPrependTo;
|
|
|
|
pItemToPrependTo->pPrevSibling->pNextSibling = pItemToPrepend;
|
|
pItemToPrependTo->pNextSibling = pItemToPrepend;
|
|
|
|
if (pItemToPrepend->pParent->pFirstChild == pItemToPrependTo) {
|
|
pItemToPrepend->pParent->pFirstChild = pItemToPrepend;
|
|
}
|
|
|
|
|
|
// Refresh the layout and redraw the tree-view control.
|
|
drgui_tv_refresh_and_redraw(pItemToPrepend->pTVElement);
|
|
}
|
|
}
|
|
|
|
|
|
bool drgui_tvi_has_children(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pItem->pFirstChild != NULL;
|
|
}
|
|
|
|
int drgui_tvi_get_depth(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem->pParent == NULL || pItem->pParent == drgui_tv_get_root_item(pItem->pTVElement)) {
|
|
return 0;
|
|
}
|
|
|
|
return drgui_tvi_get_depth(pItem->pParent) + 1;
|
|
}
|
|
|
|
drgui_tree_view_item* drgui_tvi_next_visible_non_child(drgui_tree_view_item* pItem, int* pDepthInOut)
|
|
{
|
|
if (pItem == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (pItem->pNextSibling != NULL) {
|
|
return pItem->pNextSibling;
|
|
}
|
|
|
|
|
|
if (pDepthInOut != NULL) {
|
|
*pDepthInOut -= 1;
|
|
}
|
|
|
|
return drgui_tvi_next_visible_non_child(pItem->pParent, pDepthInOut);
|
|
}
|
|
|
|
|
|
void drgui_tvi_select(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (!pItem->isSelected)
|
|
{
|
|
pItem->isSelected = true;
|
|
drgui_dirty(pItem->pTVElement, drgui_get_local_rect(pItem->pTVElement));
|
|
}
|
|
}
|
|
|
|
void drgui_tvi_deselect(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pItem->isSelected)
|
|
{
|
|
pItem->isSelected = false;
|
|
drgui_dirty(pItem->pTVElement, drgui_get_local_rect(pItem->pTVElement));
|
|
}
|
|
}
|
|
|
|
bool drgui_tvi_is_selected(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pItem->isSelected;
|
|
}
|
|
|
|
void drgui_tvi_expand(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (!pItem->isExpanded)
|
|
{
|
|
pItem->isExpanded = true;
|
|
drgui_tv_refresh_and_redraw(pItem->pTVElement);
|
|
}
|
|
}
|
|
|
|
void drgui_tvi_collapse(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (pItem->isExpanded)
|
|
{
|
|
pItem->isExpanded = false;
|
|
drgui_tv_refresh_and_redraw(pItem->pTVElement);
|
|
}
|
|
}
|
|
|
|
bool drgui_tvi_is_expanded(drgui_tree_view_item* pItem)
|
|
{
|
|
if (pItem == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return pItem->isExpanded;
|
|
}
|
|
|
|
|
|
|
|
static void drgui_tvi_detach(drgui_tree_view_item* pItem)
|
|
{
|
|
assert(pItem != NULL);
|
|
|
|
if (pItem->pParent != NULL)
|
|
{
|
|
if (pItem->pParent->pFirstChild == pItem) {
|
|
pItem->pParent->pFirstChild = pItem->pNextSibling;
|
|
}
|
|
|
|
if (pItem->pParent->pLastChild == pItem) {
|
|
pItem->pParent->pLastChild = pItem->pPrevSibling;
|
|
}
|
|
|
|
|
|
if (pItem->pPrevSibling != NULL) {
|
|
pItem->pPrevSibling->pNextSibling = pItem->pNextSibling;
|
|
}
|
|
|
|
if (pItem->pNextSibling != NULL) {
|
|
pItem->pNextSibling->pPrevSibling = pItem->pPrevSibling;
|
|
}
|
|
}
|
|
|
|
pItem->pParent = NULL;
|
|
pItem->pPrevSibling = NULL;
|
|
pItem->pNextSibling = NULL;
|
|
}
|
|
#endif //DR_GUI_IMPLEMENTATION
|
|
|
|
|
|
#ifdef DR_GUI_INCLUDE_WIP
|
|
// #include WIP files here.
|
|
#endif //DR_GUI_INCLUDE_WIP
|
|
|
|
/*
|
|
This is free and unencumbered software released into the public domain.
|
|
|
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
distribute this software, either in source code form or as a compiled
|
|
binary, for any purpose, commercial or non-commercial, and by any
|
|
means.
|
|
|
|
In jurisdictions that recognize copyright laws, the author or authors
|
|
of this software dedicate any and all copyright interest in the
|
|
software to the public domain. We make this dedication for the benefit
|
|
of the public at large and to the detriment of our heirs and
|
|
successors. We intend this dedication to be an overt act of
|
|
relinquishment in perpetuity of all present and future rights to this
|
|
software under copyright law.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
For more information, please refer to <http://unlicense.org/>
|
|
*/
|