commit 5ea1cd6e3f859618122440362fa06d56dad935ca
parent df3232724f2d62ff014f73d37f00947dd845f736
Author: Michael Savage <mikejsavage@gmail.com>
Date: Tue, 4 Sep 2018 15:38:03 +0300
Messy text highlighting
Diffstat:
5 files changed, 176 insertions(+), 35 deletions(-)
diff --git a/src/common.h b/src/common.h
@@ -22,6 +22,13 @@ constexpr T max( T a, T b ) {
return a > b ? a : b;
}
+template< typename T >
+void swap( T & a, T & b ) {
+ T t = a;
+ a = b;
+ b = t;
+}
+
template< typename To, typename From >
inline To checked_cast( const From & from ) {
To result = To( from );
diff --git a/src/textbox.cc b/src/textbox.cc
@@ -99,6 +99,37 @@ void textbox_set_size( TextBox * tb, int w, int h ) {
tb->h = h;
}
+static bool inside_selection( int col, int row, int start_col, int start_row, int end_col, int end_row ) {
+ int min_col = min( start_col, end_col );
+ int min_row = min( start_row, end_row );
+ int max_col = max( start_col, end_col );
+ int max_row = max( start_row, end_row );
+
+ if( row < min_row || row > max_row )
+ return false;
+
+ if( start_row == end_row )
+ return col >= min_col && col <= max_col;
+
+ if( row > min_row && row < max_row )
+ return true;
+
+ if( start_row < end_row ) {
+ if( row == start_row )
+ return col <= start_col;
+ if( row == end_row )
+ return col >= end_col;
+ }
+ else {
+ if( row == start_row )
+ return col >= start_col;
+ if( row == end_row )
+ return col <= end_col;
+ }
+
+ return false;
+}
+
void textbox_draw( const TextBox * tb ) {
if( tb->w == 0 || tb->h == 0 )
return;
@@ -119,6 +150,9 @@ void textbox_draw( const TextBox * tb ) {
size_t tb_rows = num_rows( tb->h );
size_t tb_cols = tb->w / fw;
+ int top_spacing = SPACING / 2;
+ int bot_spacing = SPACING - top_spacing;
+
while( rows_drawn < tb_rows && lines_drawn + tb->scroll_offset < tb->num_lines ) {
const TextBox::Line & line = tb->lines[ ( tb->head + tb->num_lines - tb->scroll_offset - lines_drawn ) % tb->max_lines ];
@@ -130,8 +164,9 @@ void textbox_draw( const TextBox * tb ) {
const TextBox::Glyph & glyph = line.glyphs[ i ];
size_t row = i / tb_cols;
+ size_t col = i % tb_cols;
- int left = ( i % tb_cols ) * fw;
+ int left = col * fw;
int top = tb->h - ( rows_drawn + line_rows - row ) * ( fh + SPACING );
if( top < 0 )
continue;
@@ -139,14 +174,21 @@ void textbox_draw( const TextBox * tb ) {
int fg, bg, bold;
unpack_style( glyph.style, &fg, &bg, &bold );
+ bool bold_fg = bold;
+ bool bold_bg = false;
+ if( tb->selecting ) {
+ if( inside_selection( col, rows_drawn + line_rows - row - 1, tb->selection_start_col, tb->selection_start_row, tb->selection_end_col, tb->selection_end_row ) ) {
+ swap( fg, bg );
+ swap( bold_fg, bold_bg );
+ }
+ }
+
// bg
// TODO: top/bottom spacing seems to be inconsistent here, try with large spacing
- int top_spacing = SPACING / 2;
- int bot_spacing = SPACING - top_spacing;
- ui_fill_rect( tb->x + left, tb->y + top - top_spacing, fw, fh + bot_spacing, Colour( bg ), false );
+ ui_fill_rect( tb->x + left, tb->y + top - top_spacing, fw, fh + bot_spacing, Colour( bg ), bold_bg );
// fg
- ui_draw_char( tb->x + left, tb->y + top, glyph.ch, Colour( fg ), bold );
+ ui_draw_char( tb->x + left, tb->y + top, glyph.ch, Colour( fg ), bold_fg, bold );
}
lines_drawn++;
diff --git a/src/textbox.h b/src/textbox.h
@@ -24,6 +24,10 @@ struct TextBox {
int x, y;
int w, h;
size_t scroll_offset;
+
+ bool selecting;
+ int selection_start_col, selection_start_row;
+ int selection_end_col, selection_end_row;
};
void textbox_init( TextBox * tb, size_t scrollback );
diff --git a/src/ui.h b/src/ui.h
@@ -38,7 +38,7 @@ void ui_chat_newline();
void ui_chat_print( const char * str, size_t len, Colour fg, Colour bg, bool bold );
void ui_fill_rect( int left, int top, int width, int height, Colour colour, bool bold );
-void ui_draw_char( int left, int top, char c, Colour colour, bool bold );
+void ui_draw_char( int left, int top, char c, Colour colour, bool bold, bool bold_font = false );
void ui_dirty( int left, int top, int right, int bottom ); // TODO: x/y + w/h?
void ui_get_font_size( int * fw, int * fh );
diff --git a/src/x11.cc b/src/x11.cc
@@ -108,7 +108,7 @@ void ui_fill_rect( int left, int top, int width, int height, Colour colour, bool
XFillRectangle( UI.display, UI.back_buffer, UI.gc, left, top, width, height );
}
-void ui_draw_char( int left, int top, char c, Colour colour, bool bold ) {
+void ui_draw_char( int left, int top, char c, Colour colour, bool bold, bool bold_font ) {
int left_spacing = Style.font.width / 2;
int right_spacing = Style.font.width - left_spacing;
int line_height = Style.font.height + SPACING;
@@ -262,7 +262,7 @@ void ui_draw_char( int left, int top, char c, Colour colour, bool bold ) {
return;
}
- XSetFont( UI.display, UI.gc, ( bold ? Style.fontBold : Style.font ).font->fid );
+ XSetFont( UI.display, UI.gc, ( bold || bold_font ? Style.fontBold : Style.font ).font->fid );
set_fg( colour, bold );
XDrawString( UI.display, UI.back_buffer, UI.gc, left, top + Style.font.ascent + SPACING, &c, 1 );
}
@@ -370,22 +370,106 @@ void ui_draw() {
ui_dirty( 0, 0, UI.width, UI.height );
}
-static void eventButtonPress( XEvent * event ) { }
+static void event_mouse_down( XEvent * xevent ) {
+ const XButtonEvent * event = &xevent->xbutton;
-static void eventButtonRelease( XEvent * event ) { }
+ if( event->x >= UI.main_text.x && event->x < UI.main_text.x + UI.main_text.w && event->y >= UI.main_text.y && event->y < UI.main_text.y + UI.main_text.h ) {
+ int x = event->x - UI.main_text.x;
+ int y = event->y - UI.main_text.y;
-static void eventMessage( XEvent * event ) {
- if( ( Atom ) event->xclient.data.l[ 0 ] == wmDeleteWindow ) {
+ int my = UI.main_text.h - y;
+
+ int row = my / ( Style.font.height + SPACING );
+ int col = x / Style.font.width;
+
+ UI.main_text.selecting = true;
+ UI.main_text.selection_start_col = col;
+ UI.main_text.selection_start_row = row;
+ UI.main_text.selection_end_col = col;
+ UI.main_text.selection_end_row = row;
+
+ textbox_draw( &UI.main_text );
+ }
+
+ if( event->x >= UI.chat_text.x && event->x < UI.chat_text.x + UI.chat_text.w && event->y >= UI.chat_text.y && event->y < UI.chat_text.y + UI.chat_text.h ) {
+ int x = event->x - UI.chat_text.x;
+ int y = event->y - UI.chat_text.y;
+
+ int my = UI.chat_text.h - y;
+
+ int row = my / ( Style.font.height + SPACING );
+ int col = x / Style.font.width;
+
+ UI.chat_text.selecting = true;
+ UI.chat_text.selection_start_col = col;
+ UI.chat_text.selection_start_row = row;
+ UI.chat_text.selection_end_col = col;
+ UI.chat_text.selection_end_row = row;
+
+ textbox_draw( &UI.chat_text );
+ }
+}
+
+static void event_mouse_up( XEvent * xevent ) {
+ const XButtonEvent * event = &xevent->xbutton;
+
+ if( UI.main_text.selecting ) {
+ UI.main_text.selecting = false;
+ textbox_draw( &UI.main_text );
+ }
+
+ if( UI.chat_text.selecting ) {
+ UI.chat_text.selecting = false;
+ textbox_draw( &UI.chat_text );
+ }
+}
+
+static void event_mouse_move( XEvent * xevent ) {
+ const XMotionEvent * event = &xevent->xmotion;
+
+ if( UI.main_text.selecting ) {
+ int x = event->x - UI.main_text.x;
+ int y = event->y - UI.main_text.y;
+
+ int my = UI.main_text.h - y;
+
+ int row = my / ( Style.font.height + SPACING );
+ int col = x / Style.font.width;
+
+ UI.main_text.selection_end_col = col;
+ UI.main_text.selection_end_row = row;
+
+ textbox_draw( &UI.main_text );
+ }
+
+ if( UI.chat_text.selecting ) {
+ int x = event->x - UI.main_text.x;
+ int y = event->y - UI.main_text.y;
+
+ int my = UI.main_text.h - y;
+
+ int row = my / ( Style.font.height + SPACING );
+ int col = x / Style.font.width;
+
+ UI.main_text.selection_end_col = col;
+ UI.main_text.selection_end_row = row;
+
+ textbox_draw( &UI.chat_text );
+ }
+}
+
+static void event_message( XEvent * xevent ) {
+ if( ( Atom ) xevent->xclient.data.l[ 0 ] == wmDeleteWindow ) {
script_handleClose();
}
}
-static void eventResize( XEvent * event ) {
+static void event_resize( XEvent * xevent ) {
int old_width = UI.width;
int old_height = UI.height;
- UI.width = event->xconfigure.width;
- UI.height = event->xconfigure.height;
+ UI.width = xevent->xconfigure.width;
+ UI.height = xevent->xconfigure.height;
if( UI.width == old_width && UI.height == old_height )
return;
@@ -412,26 +496,26 @@ static void eventResize( XEvent * event ) {
);
}
-static void eventExpose( XEvent * event ) {
+static void event_expose( XEvent * xevent ) {
ui_draw();
}
-static void eventKeyPress( XEvent * event ) {
+static void event_key_press( XEvent * xevent ) {
#define ADD_MACRO( key, name ) \
case key: \
script_doMacro( name, sizeof( name ) - 1, shift, ctrl, alt ); \
break
- XKeyEvent * keyEvent = &event->xkey;
+ XKeyEvent * event = &xevent->xkey;
char keyBuffer[ 32 ];
KeySym key;
- bool shift = keyEvent->state & ShiftMask;
- bool ctrl = keyEvent->state & ControlMask;
- bool alt = keyEvent->state & Mod1Mask;
+ bool shift = event->state & ShiftMask;
+ bool ctrl = event->state & ControlMask;
+ bool alt = event->state & Mod1Mask;
- int len = XLookupString( keyEvent, keyBuffer, sizeof( keyBuffer ), &key, NULL );
+ int len = XLookupString( event, keyBuffer, sizeof( keyBuffer ), &key, NULL );
switch( key ) {
case XK_Return:
@@ -549,11 +633,11 @@ static void eventKeyPress( XEvent * event ) {
#undef ADD_MACRO
}
-static void eventFocusOut( XEvent * event ) {
+static void event_defocus( XEvent * xevent ) {
UI.has_focus = false;
}
-static void eventFocusIn( XEvent * event ) {
+static void event_focus( XEvent * xevent ) {
UI.has_focus = true;
XWMHints * hints = XGetWMHints( UI.display, UI.window );
@@ -564,18 +648,20 @@ static void eventFocusIn( XEvent * event ) {
void ui_handleXEvents() {
void ( *event_handlers[ LASTEvent ] )( XEvent * ) = { };
- event_handlers[ ButtonPress ] = eventButtonPress;
- event_handlers[ ButtonRelease ] = eventButtonRelease;
- event_handlers[ ClientMessage ] = eventMessage;
- event_handlers[ ConfigureNotify ] = eventResize;
- event_handlers[ Expose ] = eventExpose;
- event_handlers[ KeyPress ] = eventKeyPress;
- event_handlers[ FocusOut ] = eventFocusOut;
- event_handlers[ FocusIn ] = eventFocusIn;
+ event_handlers[ ButtonPress ] = event_mouse_down;
+ event_handlers[ ButtonRelease ] = event_mouse_up;
+ event_handlers[ MotionNotify ] = event_mouse_move;
+ event_handlers[ ClientMessage ] = event_message;
+ event_handlers[ ConfigureNotify ] = event_resize;
+ event_handlers[ Expose ] = event_expose;
+ event_handlers[ KeyPress ] = event_key_press;
+ event_handlers[ FocusOut ] = event_defocus;
+ event_handlers[ FocusIn ] = event_focus;
const char * event_names[ LASTEvent ] = { };
event_names[ ButtonPress ] = "ButtonPress";
event_names[ ButtonRelease ] = "ButtonRelease";
+ event_names[ MotionNotify ] = "MotionNotify";
event_names[ ClientMessage ] = "ClientMessage";
event_names[ ConfigureNotify ] = "ConfigureNotify";
event_names[ Expose ] = "Expose";
@@ -587,8 +673,10 @@ void ui_handleXEvents() {
XEvent event;
XNextEvent( UI.display, &event );
- if( event_handlers[ event.type ] != NULL )
+ if( event_handlers[ event.type ] != NULL ) {
+ // printf( "%s\n", event_names[ event.type ] );
event_handlers[ event.type ]( &event );
+ }
}
// if( UI.dirty ) {
@@ -669,8 +757,8 @@ void ui_init() {
initStyle();
XSetWindowAttributes attr = { };
- attr.event_mask = ExposureMask | StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask,
- attr.colormap = UI.colorMap,
+ attr.event_mask = ExposureMask | StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask;
+ attr.colormap = UI.colorMap;
UI.window = XCreateWindow( UI.display, root, 0, 0, 800, 600, 0, UI.depth, InputOutput, visual, CWBackPixel | CWEventMask | CWColormap, &attr );
UI.gc = XCreateGC( UI.display, UI.window, 0, NULL );