Spinner and multiline textbox widgets (#8199)

This commit is contained in:
Subhraman Sarkar 2024-02-13 23:24:34 +05:30 committed by GitHub
parent bded0465fc
commit 6c5a8e923d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 2961 additions and 43 deletions

View file

@ -0,0 +1,258 @@
#textdomain wesnoth-lib
###
### Definition of a basic multiline text box.
### The base widget for a text box that supports
### editing multiple lines of text.
### Cannot be used on its own, use [scroll_text] instead,
### which is a multiline text box with scrollbars.
#define _GUI_DRAW_BACKGROUND COLOR
[rectangle]
x = 0
y = 0
w = "(width)"
h = "(height)"
fill_color = {COLOR}
[/rectangle]
#enddef
#define _GUI_TEXTBOX_BACKGROUND_ENABLED
{_GUI_DRAW_BACKGROUND ({GUI__BACKGROUND_COLOR_ENABLED})}
#enddef
#define _GUI_TEXTBOX_BACKGROUND_DISABLED
{_GUI_DRAW_BACKGROUND ({GUI__BACKGROUND_COLOR_DISABLED})}
#enddef
#define _GUI_DRAW_BORDER COLOR
[rectangle]
x = 0
y = 0
w = "(width)"
h = "(height)"
border_thickness = 1
border_color = {COLOR}
[/rectangle]
#enddef
#define _GUI_DRAW_TEXT SIZE COLOR
#arg FONT_FAMILY
#endarg
##### The text
[text]
x = "(text_x_offset)"
y = "(text_y_offset)"
w = "(text_width)"
h = "(text_height)"
maximum_width = "(text_maximum_width)"
font_size = {SIZE}
font_family = {FONT_FAMILY}
color = "(if(editable=1, active_color, inactive_color) where
active_color = [{COLOR}],
inactive_color = [{__DISABLED_COLOR_PREPROCESSOR_WORKAROUND}]
)"
text = "(text)"
highlight_color = "21, 53, 80"
highlight_start = "(highlight_start)"
highlight_end = "(highlight_end)"
[/text]
#enddef
#
# The preprocessor can't process GUI__FONT_COLOR_DISABLED__DEFAULT as part of string
# concatenation for some reason. Perhaps it's the optional argument. So I'm just copying
# it here. Though the lack of quotes means I don't need to use concatenation anyway.
#
# -- vultraz, 2018-03-26
#
#define __DISABLED_COLOR_PREPROCESSOR_WORKAROUND
128, 128, 128, 255 #enddef
#define _GUI_DRAW_TEXT_OR_HINT SIZE COLOR
#arg FONT_FAMILY
#endarg
[rectangle]
x = "(text_x_offset + selection_start_x)"
y = "(text_y_offset)"
w = "(selection_width)"
h = "(text_font_height + selection_height)"
border_thickness = 0
fill_color = "21, 53, 80, 255"
[/rectangle]
[text]
x = "(text_x_offset)"
y = "(text_y_offset)"
w = "(text_width)"
h = "(text_height)"
maximum_width = "(text_maximum_width)"
font_size = "(
if(text = '' and hint_text != '', hint_size, reg_size) where
hint_size = {GUI_FONT_SIZE_SMALL},
reg_size = {SIZE}
)"
font_family = {FONT_FAMILY}
color = "(
if((text = '' and hint_text != '') or editable = 0, hint_color, reg_color) where
hint_color = [{__DISABLED_COLOR_PREPROCESSOR_WORKAROUND}],
reg_color = [{COLOR}]
)"
text = "(
if(text = '' and hint_text != '', hint_text, text))"
[/text]
[image]
x = "(width - image_width)"
y = "(max(0, height / 2 - image_height / 2))"
name = "(if(text = '' and hint_image != '', hint_image, ''))"
[/image]
#enddef
#define _GUI_DRAW_CURSOR X_OFFSET
[line]
x1 = "(cursor_offset_x + {X_OFFSET})"
y1 = "(text_y_offset + cursor_offset_y + 2)"
x2 = "(cursor_offset_x + {X_OFFSET})"
y2 = "(text_y_offset + cursor_offset_y + text_font_height - 2)"
color = "([255, 255, 255, cursor_alpha])"
thickness = 1
[/line]
[rectangle]
x = "(composition_offset + {X_OFFSET})"
y = "(text_y_offset + text_font_height - 2)"
w = "(composition_width)"
h = "2"
fill_color = "([140, 140, 0, if(composition_width > 0, 255, 0)])"
border_thickness = 0
[/rectangle]
#enddef
#define _GUI_RESOLUTION RESOLUTION MIN_WIDTH DEFAULT_WIDTH HEIGHT X_OFFSET EXTRA_WIDTH FONT_SIZE BACKGROUND_ENABLED BACKGROUND_DISABLED
#arg FONT_FAMILY
#endarg
[resolution]
{RESOLUTION}
min_width = {MIN_WIDTH}
min_height = {HEIGHT}
default_width = {DEFAULT_WIDTH}
default_height = {HEIGHT}
max_width = 0
max_height = {HEIGHT}
text_font_size = {FONT_SIZE}
text_font_family = {FONT_FAMILY}
text_x_offset = {X_OFFSET}
text_y_offset = 2
text_extra_width = {EXTRA_WIDTH}
#functions = "(def show_hint_text() (text = '' and hint_text != '');)"
[state_enabled]
[draw]
{BACKGROUND_ENABLED}
{_GUI_DRAW_BORDER ({GUI__BORDER_COLOR_DARK}) }
{_GUI_DRAW_TEXT_OR_HINT ({FONT_SIZE}) ({GUI__FONT_COLOR_ENABLED__DEFAULT}) FONT_FAMILY={FONT_FAMILY} }
[/draw]
[/state_enabled]
[state_disabled]
[draw]
{BACKGROUND_DISABLED}
{_GUI_DRAW_BORDER ({GUI__FONT_COLOR_DISABLED_DARK__DEFAULT}) }
{_GUI_DRAW_TEXT_OR_HINT ({FONT_SIZE}) ({GUI__FONT_COLOR_DISABLED__DEFAULT}) FONT_FAMILY={FONT_FAMILY} }
[/draw]
[/state_disabled]
[state_focused]
[draw]
{BACKGROUND_ENABLED}
{_GUI_DRAW_BORDER ({GUI__BORDER_COLOR_BRIGHT}) }
# We never draw the hint text or image if focused
{_GUI_DRAW_TEXT ({FONT_SIZE}) ({GUI__FONT_COLOR_ENABLED__DEFAULT}) FONT_FAMILY={FONT_FAMILY} }
{_GUI_DRAW_CURSOR ({X_OFFSET}) }
[/draw]
[/state_focused]
[state_hovered]
[draw]
{BACKGROUND_ENABLED}
{_GUI_DRAW_BORDER ({GUI__BORDER_COLOR}) }
{_GUI_DRAW_TEXT_OR_HINT ({FONT_SIZE}) ({GUI__FONT_COLOR_ENABLED__DEFAULT}) FONT_FAMILY={FONT_FAMILY} }
[/draw]
[/state_hovered]
[/resolution]
#enddef
[multiline_text_definition]
id = "default"
description = "Default multiline text box"
{_GUI_RESOLUTION () 40 250 90 5 10 ({GUI_FONT_SIZE_DEFAULT}) ({_GUI_TEXTBOX_BACKGROUND_ENABLED}) ({_GUI_TEXTBOX_BACKGROUND_DISABLED})}
[/multiline_text_definition]
[multiline_text_definition]
id = "transparent"
description = "Background-less multiline text box, used for WML messages"
{_GUI_RESOLUTION () 40 250 90 5 10 ({GUI_FONT_SIZE_DEFAULT}) () ()}
[/multiline_text_definition]
[multiline_text_definition]
id = "verbatim"
description = "Monospace multiline text box"
{_GUI_RESOLUTION () 40 250 90 5 10 ({GUI_FONT_SIZE_DEFAULT}) ({_GUI_TEXTBOX_BACKGROUND_ENABLED}) ({_GUI_TEXTBOX_BACKGROUND_DISABLED}) FONT_FAMILY=monospace}
[/multiline_text_definition]
#undef _GUI_RESOLUTION
#undef _GUI_DRAW_CURSOR
#undef _GUI_DRAW_TEXT
#undef _GUI_DRAW_BORDER
#undef _GUI_DRAW_BACKGROUND
#undef _GUI_DRAW_TEXT_OR_HINT
#undef _GUI_TEXTBOX_BACKGROUND_ENABLED
#undef _GUI_TEXTBOX_BACKGROUND_DISABLED
#undef __DISABLED_COLOR_PREPROCESSOR_WORKAROUND

View file

@ -0,0 +1,127 @@
#textdomain wesnoth-lib
###
### Definition of a scrollable multiline text box.
### A text box that support editing multiline text and
### resizes/shows scrollbar depending on the entered text.
### Use this instead of [multiline_text], which cannot be used by itself.
###
#define _GUI_RESOLUTION RESOLUTION DEFINITION FONT_SIZE FONT_STYLE FONT_COLOR_ENABLED FONT_COLOR_DISABLED
[resolution]
{RESOLUTION}
min_width = 0
min_height = 0
default_width = 0
default_height = 0
max_width = 0
max_height = 0
text_font_size = {FONT_SIZE}
text_font_style = {FONT_STYLE}
[state_enabled]
[draw]
[/draw]
[/state_enabled]
[state_disabled]
[draw]
[/draw]
[/state_disabled]
[grid]
[row]
grow_factor = 1
[column]
grow_factor = 1
horizontal_grow = true
vertical_grow = true
[grid]
id = "_content_grid"
[row]
[column]
border = "right,bottom" # between the text and the scrollbars
border_size = 3 # intentionally smaller than normal
horizontal_grow = true
vertical_grow = true
[multiline_text]
id="_text"
definition="default"
[/multiline_text]
[/column]
[/row]
[/grid]
[/column]
[column]
{GUI__VERTICAL_SCROLLBAR_GRID}
[/column]
[/row]
[row]
[column]
{GUI__HORIZONTAL_SCROLLBAR_GRID}
[/column]
[column]
[spacer]
[/spacer]
[/column]
[/row]
[/grid]
[/resolution]
#enddef
#define _GUI_DEFINITION ID DESCRIPTION DEFINITION
[scroll_text_definition]
id = {ID}
description = {DESCRIPTION}
{_GUI_RESOLUTION
()
({DEFINITION})
({GUI_FONT_SIZE_DEFAULT})
()
({GUI__FONT_COLOR_ENABLED__DEFAULT})
({GUI__FONT_COLOR_DISABLED__DEFAULT})
}
[/scroll_text_definition]
#enddef
{_GUI_DEFINITION "default" "The default scroll label." "default"}
{_GUI_DEFINITION "default_large" "Large font size scroll label." "default_large"}
{_GUI_DEFINITION "default_small" "Small font size scroll label." "default_small"}
{_GUI_DEFINITION "default_italic" "Small font size scroll label." "default_italic"}
#undef _GUI_DEFINITION
#undef _GUI_RESOLUTION

View file

@ -0,0 +1,130 @@
#textdomain wesnoth-lib
###
### Definition of a scrollable multiline text area.
### A text box that support editing multiline text and
### resizes/shows scrollbar depending on the entered text.
### Use this instead of [multiline_text].
### This version is used for editing verbatim text
### (such as WML snippets) using a fixed-width font.
#define _GUI_RESOLUTION RESOLUTION FONT_SIZE FONT_STYLE FONT_COLOR_ENABLED FONT_COLOR_DISABLED
[resolution]
{RESOLUTION}
min_width = 0
min_height = 0
default_width = 0
default_height = 0
max_width = 0
max_height = 0
text_font_size = {FONT_SIZE}
text_font_style = {FONT_STYLE}
text_font_family = monospace
[state_enabled]
[draw]
[rectangle]
x = 0
y = 0
w = "(width)"
h = "(height)"
fill_color = {GUI__BACKGROUND_COLOR_ENABLED}
[/rectangle]
[/draw]
[/state_enabled]
[state_disabled]
[draw]
[/draw]
[/state_disabled]
[grid]
[row]
grow_factor = 1
[column]
grow_factor = 1
horizontal_grow = true # needed ?
vertical_grow = true # needed ?
[grid]
id = "_content_grid"
[row]
[column]
horizontal_grow = true
vertical_grow = true
border = "all"
border_size = 5
[multiline_text]
id="_text"
definition="verbatim"
[/multiline_text]
[/column]
[/row]
[/grid]
[/column]
[column]
{GUI__VERTICAL_SCROLLBAR_GRID}
[/column]
[/row]
[row]
[column]
{GUI__HORIZONTAL_SCROLLBAR_GRID}
[/column]
[column]
[spacer]
[/spacer]
[/column]
[/row]
[/grid]
[/resolution]
#enddef
[scroll_text_definition]
id = "verbatim"
description = "A scroll label type used for verbatim text using a fixed-width font."
{_GUI_RESOLUTION
()
({GUI_FONT_SIZE_DEFAULT})
()
({GUI__FONT_COLOR_ENABLED__DEFAULT})
({GUI__FONT_COLOR_DISABLED__DEFAULT})
}
[/scroll_text_definition]
#undef _GUI_RESOLUTION

View file

@ -0,0 +1,223 @@
#textdomain wesnoth-lib
###
### Definition of a spinner widget.
### A widget with a text box and two buttons (_prev and _next) that is used for numerical entry.
### Clicking the _prev and _next buttons reduces and increases the numerical value in the text box.
### Non-numeric values are replaced with zero when any of the buttons are pressed.
###
#define GRID_SPLIT ALIGN
[grid]
id = "_content_grid"
[row]
grow_factor = 0
[column]
horizontal_alignment={ALIGN}
[grid]
[row]
[column]
horizontal_alignment={ALIGN}
[repeating_button]
id= "_prev"
definition = "left_arrow"
[/repeating_button]
[/column]
[column]
border = "left,right"
border_size = 3
horizontal_alignment={ALIGN}
[text_box]
id = "_text"
[/text_box]
[/column]
[column]
horizontal_alignment={ALIGN}
[repeating_button]
id= "_next"
definition = "right_arrow"
[/repeating_button]
[/column]
[/row]
[/grid]
[/column]
[/row]
[/grid]
#enddef
#define GRID_LEFT ALIGN
[grid]
id = "_content_grid"
[row]
grow_factor = 0
[column]
horizontal_alignment={ALIGN}
[grid]
[row]
[column]
horizontal_alignment={ALIGN}
[repeating_button]
id= "_prev"
definition = "left_arrow"
[/repeating_button]
[/column]
[column]
border = "left"
border_size = 3
horizontal_alignment={ALIGN}
[repeating_button]
id= "_next"
definition = "right_arrow"
[/repeating_button]
[/column]
[column]
border = "left"
border_size = 3
horizontal_alignment={ALIGN}
[text_box]
id = "_text"
[/text_box]
[/column]
[/row]
[/grid]
[/column]
[/row]
[/grid]
#enddef
#define GRID_RIGHT ALIGN
[grid]
id = "_content_grid"
[row]
grow_factor = 0
[column]
horizontal_alignment={ALIGN}
[grid]
[row]
[column]
border = "right"
border_size = 3
horizontal_alignment={ALIGN}
[text_box]
id = "_text"
[/text_box]
[/column]
[column]
border = "right"
border_size = 3
horizontal_alignment={ALIGN}
[repeating_button]
id= "_prev"
definition = "left_arrow"
[/repeating_button]
[/column]
[column]
horizontal_alignment={ALIGN}
[repeating_button]
id= "_next"
definition = "right_arrow"
[/repeating_button]
[/column]
[/row]
[/grid]
[/column]
[/row]
[/grid]
#enddef
#define _GUI_RESOLUTION RESOLUTION DEFINITION FONT_SIZE FONT_STYLE FONT_COLOR_ENABLED FONT_COLOR_DISABLED GRID
[resolution]
{RESOLUTION}
min_width = 0
min_height = 0
default_width = 0
default_height = 0
max_width = 0
max_height = 0
text_font_size = {FONT_SIZE}
text_font_style = {FONT_STYLE}
[state_enabled]
[draw]
[/draw]
[/state_enabled]
[state_disabled]
[draw]
[/draw]
[/state_disabled]
{GRID}
[/resolution]
#enddef
[spinner_definition]
id = "default"
description = "Default Spinner with left and right arrows, split on both sides"
{_GUI_RESOLUTION
()
()
({GUI_FONT_SIZE_DEFAULT})
()
({GUI__FONT_COLOR_ENABLED__DEFAULT})
({GUI__FONT_COLOR_DISABLED__DEFAULT})
({GRID_SPLIT ("left")})
}
[/spinner_definition]
[spinner_definition]
id = "left"
description = "Default Spinner with both left and right arrows on the left"
{_GUI_RESOLUTION
()
()
({GUI_FONT_SIZE_DEFAULT})
()
({GUI__FONT_COLOR_ENABLED__DEFAULT})
({GUI__FONT_COLOR_DISABLED__DEFAULT})
({GRID_LEFT ("left")})
}
[/spinner_definition]
[spinner_definition]
id = "right"
description = "Default Spinner with both left and right arrows on the right"
{_GUI_RESOLUTION
()
()
({GUI_FONT_SIZE_DEFAULT})
()
({GUI__FONT_COLOR_ENABLED__DEFAULT})
({GUI__FONT_COLOR_DISABLED__DEFAULT})
({GRID_RIGHT ("left")})
}
[/spinner_definition]
#undef _GUI_RESOLUTION

View file

@ -136,7 +136,9 @@
{DEFAULT_KEY "font_family" font_family "sans"}
{REQUIRED_KEY "font_size" f_unsigned}
{DEFAULT_KEY "font_style" font_style ""}
{DEFAULT_KEY "h" f_unsigned 0}
{DEFAULT_KEY "highlight_color" string "#215380"}
{DEFAULT_KEY "highlight_start" f_unsigned 0}
{DEFAULT_KEY "highlight_end" f_unsigned 0}
{DEFAULT_KEY "maximum_height" f_int -1}
{DEFAULT_KEY "maximum_width" f_int -1}
{DEFAULT_KEY "text" f_t_string ""}
@ -144,6 +146,8 @@
{DEFAULT_KEY "text_markup" f_bool false}
{DEFAULT_KEY "text_link_aware" f_bool false}
{DEFAULT_KEY "text_link_color" string "#ffff00"}
{DEFAULT_KEY "text_wrap_mode" f_unsigned 0}
{DEFAULT_KEY "h" f_unsigned 0}
{DEFAULT_KEY "w" f_unsigned 0}
{DEFAULT_KEY "x" f_unsigned 0}
{DEFAULT_KEY "y" f_unsigned 0}
@ -200,4 +204,4 @@
{DEFAULT_KEY "use_markup" bool false}
{DEFAULT_KEY "can_shrink" bool false}
[/tag]
[/tag]
[/tag]

View file

@ -259,6 +259,44 @@
{LINK_TAG "$cell/grid"}
[/tag]
[/tag]
[tag]
name="multiline_text_definition"
min="0"
max="infinite"
super="$generic/widget_definition"
[tag]
name="resolution"
min="0"
max="infinite"
super="$generic/widget_definition/resolution"
[tag]
name="state_disabled"
min="0"
max="1"
super="$generic/state"
[/tag]
[tag]
name="state_enabled"
min="0"
max="1"
super="$generic/state"
[/tag]
[tag]
name="state_focused"
min="0"
max="1"
super="$generic/state"
[/tag]
[tag]
name="state_hovered"
min="0"
max="1"
super="$generic/state"
[/tag]
{DEFAULT_KEY "text_x_offset" f_unsigned ""}
{DEFAULT_KEY "text_y_offset" f_unsigned ""}
[/tag]
[/tag]
[tag]
name="multi_page_definition"
min="0"
@ -391,6 +429,31 @@
{LINK_TAG "$cell/grid"}
[/tag]
[/tag]
[tag]
name="scroll_text_definition"
min="0"
max="infinite"
super="$generic/widget_definition"
[tag]
name="resolution"
min="0"
max="infinite"
super="$generic/widget_definition/resolution"
[tag]
name="state_disabled"
min="0"
max="1"
super="$generic/state"
[/tag]
[tag]
name="state_enabled"
min="0"
max="1"
super="$generic/state"
[/tag]
{LINK_TAG "$cell/grid"}
[/tag]
[/tag]
[tag]
name="scrollbar_panel_definition"
min="0"
@ -477,6 +540,31 @@
max="infinite"
super="$generic/widget_definition"
[/tag]
[tag]
name="spinner_definition"
min="0"
max="infinite"
super="$generic/widget_definition"
[tag]
name="resolution"
min="0"
max="infinite"
super="$generic/widget_definition/resolution"
[tag]
name="state_disabled"
min="0"
max="1"
super="$generic/state"
[/tag]
[tag]
name="state_enabled"
min="0"
max="1"
super="$generic/state"
[/tag]
{LINK_TAG "$cell/grid"}
[/tag]
[/tag]
[tag]
name="stacked_widget_definition"
min="0"

View file

@ -58,6 +58,19 @@
{DEFAULT_KEY "details" t_string ""}
[/tag]
[/tag]
[tag]
name="multiline_text"
min="0"
max="infinite"
super="$generic/widget_instance"
{DEFAULT_KEY "history" string ""}
{DEFAULT_KEY "max_input_length" int 0}
{DEFAULT_KEY "label" t_string ""}
{DEFAULT_KEY "hint_text" t_string ""}
{DEFAULT_KEY "hint_image" string ""}
{DEFAULT_KEY "editable" bool true}
{DEFAULT_KEY "wrap" bool true}
[/tag]
[tag]
name="multimenu_button"
min="0"
@ -357,6 +370,25 @@
{DEFAULT_KEY "text_alignment" f_h_align "left"}
{DEFAULT_KEY "link_aware" bool false}
[/tag]
[tag]
name="spinner"
min="0"
max="infinite"
super="$generic/widget_instance"
{DEFAULT_KEY "wrap" bool true}
[/tag]
[tag]
name="scroll_text"
min="0"
max="infinite"
super="$generic/widget_instance"
{DEFAULT_KEY "horizontal_scrollbar_mode" scrollbar_mode initial_auto}
{DEFAULT_KEY "vertical_scrollbar_mode" scrollbar_mode initial_auto}
{DEFAULT_KEY "wrap" bool true}
{DEFAULT_KEY "text_alignment" f_h_align "left"}
{DEFAULT_KEY "link_aware" bool false}
{DEFAULT_KEY "editable" bool true}
[/tag]
[tag]
name="scrollbar_panel"
min="0"

View file

@ -804,6 +804,8 @@
<Unit filename="../../src/gui/widgets/minimap.hpp" />
<Unit filename="../../src/gui/widgets/multi_page.cpp" />
<Unit filename="../../src/gui/widgets/multi_page.hpp" />
<Unit filename="../../src/gui/widgets/multiline_text.cpp" />
<Unit filename="../../src/gui/widgets/multiline_text.hpp" />
<Unit filename="../../src/gui/widgets/multimenu_button.cpp" />
<Unit filename="../../src/gui/widgets/multimenu_button.hpp" />
<Unit filename="../../src/gui/widgets/pane.cpp" />
@ -819,6 +821,8 @@
<Unit filename="../../src/gui/widgets/retval.hpp" />
<Unit filename="../../src/gui/widgets/scroll_label.cpp" />
<Unit filename="../../src/gui/widgets/scroll_label.hpp" />
<Unit filename="../../src/gui/widgets/scroll_text.cpp" />
<Unit filename="../../src/gui/widgets/scroll_text.hpp" />
<Unit filename="../../src/gui/widgets/scrollbar.cpp" />
<Unit filename="../../src/gui/widgets/scrollbar.hpp" />
<Unit filename="../../src/gui/widgets/scrollbar_container.cpp" />
@ -837,6 +841,8 @@
<Unit filename="../../src/gui/widgets/slider_base.hpp" />
<Unit filename="../../src/gui/widgets/spacer.cpp" />
<Unit filename="../../src/gui/widgets/spacer.hpp" />
<Unit filename="../../src/gui/widgets/spinner.cpp" />
<Unit filename="../../src/gui/widgets/spinner.hpp" />
<Unit filename="../../src/gui/widgets/stacked_widget.cpp" />
<Unit filename="../../src/gui/widgets/stacked_widget.hpp" />
<Unit filename="../../src/gui/widgets/status_label_helper.hpp" />

View file

@ -19,6 +19,7 @@
1234567890ABCDEF12345678 /* file_progress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1234567890ABCDEF12345680 /* file_progress.cpp */; };
1234567890ABCDEF12345679 /* file_progress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1234567890ABCDEF12345680 /* file_progress.cpp */; };
172E48A5BD149999CE64EDF8 /* prompt.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D4594633BF3F8A06D6AE752F /* prompt.hpp */; };
179D4E93A08C5A67B071C6C1 /* spinner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4214F3DA80B54080C4B548F /* spinner.cpp */; };
36B146FAA79A55E9F43723B1 /* general.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84234C54BB84519421FD4136 /* general.cpp */; };
36D74F7F8D7655ACCABE562D /* edit_pbl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6FA542D78393E8FF067775DA /* edit_pbl.cpp */; };
3C254DF5B7DF196F2041955F /* mp_report.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 58C649488B3014E6F7254B62 /* mp_report.cpp */; };
@ -658,6 +659,7 @@
62D24F321519987400350848 /* context_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 62D24F311519987400350848 /* context_manager.cpp */; };
62D24F351519995200350848 /* palette_manager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 62D24F341519995200350848 /* palette_manager.cpp */; };
6D574EACA3483ABEE72819F0 /* statistics_record.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27764FB68F02032F1C0B6748 /* statistics_record.cpp */; };
77D94146A5FA29849D1A9BD8 /* multiline_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0B0F48CE9CF65D9813BE6CDC /* multiline_text.cpp */; };
7BFC4DF5BFF8CF75855BA662 /* prompt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 67044415B63F5888193BD7A6 /* prompt.cpp */; };
7FDF4E8D9C94E7DA8F41F7BB /* tod_new_schedule.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 5D46466DBCD81B13621C7342 /* tod_new_schedule.hpp */; };
867141839BDB89BFE876E310 /* carryover_show_gold.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 09A440B1A671C45BE2924FB4 /* carryover_show_gold.cpp */; };
@ -1266,8 +1268,11 @@
C0454A6592DCD67D93323D9C /* edit_pbl.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B2CC45FEA71445AE817CAA6B /* edit_pbl.hpp */; };
C93048A9AE576B6AD016DFA4 /* tod_new_schedule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61F473D9AC43768A445E218 /* tod_new_schedule.cpp */; };
C9D14DC39B87182A00397A3D /* choose_addon.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D9A141EAAE90E98B6F6171D6 /* choose_addon.hpp */; };
D1254FCA82471825B83AA786 /* multiline_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0B0F48CE9CF65D9813BE6CDC /* multiline_text.cpp */; };
DF974010BB46B844E83F7DDA /* tod_new_schedule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61F473D9AC43768A445E218 /* tod_new_schedule.cpp */; };
E1DA41878F0C255769B8239D /* scroll_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 755D4555A1DEA29125E7F338 /* scroll_text.cpp */; };
E2F24C0CBC863C3DC8A041EF /* edit_pbl.hpp in Headers */ = {isa = PBXBuildFile; fileRef = B2CC45FEA71445AE817CAA6B /* edit_pbl.hpp */; };
E6CF415F9FD04C35A55FB24D /* scroll_text.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 755D4555A1DEA29125E7F338 /* scroll_text.cpp */; };
E79249078ACE777D1E219DED /* choose_addon.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 20E644DC98F26C756364EC2C /* choose_addon.cpp */; };
EBB44A70837B84A928CB6424 /* edit_pbl_translation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 00574699A982AA23F12B39E0 /* edit_pbl_translation.cpp */; };
EC0341E11ECF46FE000F2E2B /* config_attribute_value.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC0341DF1ECF46FE000F2E2B /* config_attribute_value.cpp */; };
@ -1420,6 +1425,7 @@
ECFB9FA8193BFAD900146ED0 /* carryover.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ECFB9FA7193BFAD900146ED0 /* carryover.cpp */; };
ECFB9FAA193BFB4B00146ED0 /* game_board.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ECFB9FA9193BFB4B00146ED0 /* game_board.cpp */; };
ECFB9FAC193BFB6E00146ED0 /* rect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ECFB9FAB193BFB6E00146ED0 /* rect.cpp */; };
F13D4C33BAA4CB9E9AACFCC2 /* spinner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E4214F3DA80B54080C4B548F /* spinner.cpp */; };
F40A13BC1A3A88BA00C4D071 /* apple_notification.mm in Sources */ = {isa = PBXBuildFile; fileRef = F40A13BB1A3A88BA00C4D071 /* apple_notification.mm */; };
F419A1F414E21246002F9ADC /* game_end_exceptions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F419A1F314E21246002F9ADC /* game_end_exceptions.cpp */; };
F419A2C314F5BCFE002F9ADC /* info.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F419A2C114F5BCFE002F9ADC /* info.cpp */; };
@ -1553,6 +1559,7 @@
000000000000000000000010 /* network_download_file.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = network_download_file.hpp; sourceTree = "<group>"; };
00574699A982AA23F12B39E0 /* edit_pbl_translation.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = edit_pbl_translation.cpp; sourceTree = "<group>"; };
09A440B1A671C45BE2924FB4 /* carryover_show_gold.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = carryover_show_gold.cpp; sourceTree = "<group>"; };
0B0F48CE9CF65D9813BE6CDC /* multiline_text.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = multiline_text.cpp; path = multiline_text.cpp; sourceTree = "<group>"; };
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
1234567890ABCDEF12345680 /* file_progress.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = file_progress.cpp; sourceTree = "<group>"; };
1234567890ABCDEF12345681 /* file_progress.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = file_progress.hpp; sourceTree = "<group>"; };
@ -2099,7 +2106,7 @@
49478712172FF6F8002B7ABA /* tristate_button.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tristate_button.cpp; sourceTree = "<group>"; };
49478713172FF6F8002B7ABA /* tristate_button.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tristate_button.hpp; sourceTree = "<group>"; };
58C649488B3014E6F7254B62 /* mp_report.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = mp_report.cpp; sourceTree = "<group>"; };
5D46466DBCD81B13621C7342 /* tod_new_schedule.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tod_new_schedule.hpp; sourceTree = "<group>"; };
5D46466DBCD81B13621C7342 /* tod_new_schedule.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tod_new_schedule.hpp; path = tod_new_schedule.hpp; sourceTree = "<group>"; };
620A386215E9364E00A4F513 /* attack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = attack.cpp; sourceTree = "<group>"; };
620A386315E9364E00A4F513 /* attack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = attack.hpp; sourceTree = "<group>"; };
620A386415E9364E00A4F513 /* create.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = create.cpp; sourceTree = "<group>"; };
@ -2183,6 +2190,7 @@
62D24F341519995200350848 /* palette_manager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = palette_manager.cpp; sourceTree = "<group>"; };
67044415B63F5888193BD7A6 /* prompt.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = prompt.cpp; sourceTree = "<group>"; };
6FA542D78393E8FF067775DA /* edit_pbl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = edit_pbl.cpp; sourceTree = "<group>"; };
755D4555A1DEA29125E7F338 /* scroll_text.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = scroll_text.cpp; path = scroll_text.cpp; sourceTree = "<group>"; };
84234C54BB84519421FD4136 /* general.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = general.cpp; sourceTree = "<group>"; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8D1107320486CEB800E47090 /* The Battle for Wesnoth.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "The Battle for Wesnoth.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -2709,6 +2717,7 @@
D4594633BF3F8A06D6AE752F /* prompt.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = prompt.hpp; sourceTree = "<group>"; };
D911474D925FA88D5B856A0E /* test_sdl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = test_sdl.cpp; path = test_sdl.cpp; sourceTree = "<group>"; };
D9A141EAAE90E98B6F6171D6 /* choose_addon.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = choose_addon.hpp; sourceTree = "<group>"; };
E4214F3DA80B54080C4B548F /* spinner.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = spinner.cpp; path = spinner.cpp; sourceTree = "<group>"; };
EC0341DF1ECF46FE000F2E2B /* config_attribute_value.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = config_attribute_value.cpp; sourceTree = "<group>"; };
EC0341E01ECF46FE000F2E2B /* config_attribute_value.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = config_attribute_value.hpp; sourceTree = "<group>"; };
EC0680231EA920A300EEE03B /* random_deterministic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = random_deterministic.cpp; sourceTree = "<group>"; };
@ -4064,6 +4073,9 @@
46F92D342174F6A300602C1C /* window_private.hpp */,
46F92D2E2174F6A300602C1C /* window.cpp */,
46F92D472174F6A300602C1C /* window.hpp */,
E4214F3DA80B54080C4B548F /* spinner.cpp */,
0B0F48CE9CF65D9813BE6CDC /* multiline_text.cpp */,
755D4555A1DEA29125E7F338 /* scroll_text.cpp */,
);
path = widgets;
sourceTree = "<group>";
@ -5866,6 +5878,9 @@
0554467DB5FE99D85ABCDCA0 /* edit_pbl_translation.cpp in Sources */,
8C704B6F99C824A4073EEBEE /* prompt.cpp in Sources */,
DF974010BB46B844E83F7DDA /* tod_new_schedule.cpp in Sources */,
179D4E93A08C5A67B071C6C1 /* spinner.cpp in Sources */,
77D94146A5FA29849D1A9BD8 /* multiline_text.cpp in Sources */,
E1DA41878F0C255769B8239D /* scroll_text.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -6543,6 +6558,9 @@
EBB44A70837B84A928CB6424 /* edit_pbl_translation.cpp in Sources */,
7BFC4DF5BFF8CF75855BA662 /* prompt.cpp in Sources */,
C93048A9AE576B6AD016DFA4 /* tod_new_schedule.cpp in Sources */,
F13D4C33BAA4CB9E9AACFCC2 /* spinner.cpp in Sources */,
D1254FCA82471825B83AA786 /* multiline_text.cpp in Sources */,
E6CF415F9FD04C35A55FB24D /* scroll_text.cpp in Sources */,
62714C2FBE84B66CF14E3722 /* test_sdl.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View file

@ -10,16 +10,19 @@ gui/widgets/matrix.cpp
gui/widgets/menu_button.cpp
gui/widgets/minimap.cpp
gui/widgets/multi_page.cpp
gui/widgets/multiline_text.cpp
gui/widgets/multimenu_button.cpp
gui/widgets/panel.cpp
gui/widgets/password_box.cpp
gui/widgets/progress_bar.cpp
gui/widgets/repeating_button.cpp
gui/widgets/scroll_label.cpp
gui/widgets/scroll_text.cpp
gui/widgets/scrollbar_panel.cpp
gui/widgets/size_lock.cpp
gui/widgets/slider.cpp
gui/widgets/spacer.cpp
gui/widgets/spinner.cpp
gui/widgets/stacked_widget.cpp
gui/widgets/text_box.cpp
gui/widgets/toggle_button.cpp

View file

@ -272,6 +272,7 @@ gui/widgets/generator.cpp
gui/widgets/grid.cpp
gui/widgets/helper.cpp
gui/widgets/pane.cpp
gui/widgets/scroll_text.cpp
gui/widgets/scrollbar.cpp
gui/widgets/scrollbar_container.cpp
gui/widgets/settings.cpp

View file

@ -97,7 +97,7 @@ pango_text::pango_text()
pango_layout_set_ellipsize(layout_.get(), ellipse_mode_);
pango_layout_set_alignment(layout_.get(), alignment_);
pango_layout_set_wrap(layout_.get(), PANGO_WRAP_WORD_CHAR);
pango_layout_set_line_spacing(layout_.get(), 1.3f);
pango_layout_set_line_spacing(layout_.get(), get_line_spacing_factor());
cairo_font_options_t *fo = cairo_font_options_create();
cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
@ -193,18 +193,41 @@ unsigned pango_text::insert_text(const unsigned offset, const std::string& text)
return len;
}
int pango_text::get_byte_offset(const unsigned column) const
{
// First we need to determine the byte offset
std::unique_ptr<PangoLayoutIter, std::function<void(PangoLayoutIter*)>> itor(
pango_layout_get_iter(layout_.get()), pango_layout_iter_free);
// Go the wanted column.
for(std::size_t i = 0; i < column; ++i) {
if(!pango_layout_iter_next_char(itor.get())) {
// It seems that the documentation is wrong and causes and off by
// one error... the result should be false if already at the end of
// the data when started.
if(i + 1 == column) {
break;
}
}
}
// Get the byte offset
const int offset = pango_layout_iter_get_index(itor.get());
return offset;
}
point pango_text::get_cursor_position(const unsigned column, const unsigned line) const
{
this->recalculate();
// First we need to determine the byte offset, if more routines need it it
// would be a good idea to make it a separate function.
// Determing byte offset
std::unique_ptr<PangoLayoutIter, std::function<void(PangoLayoutIter*)>> itor(
pango_layout_get_iter(layout_.get()), pango_layout_iter_free);
// Go the wanted line.
if(line != 0) {
if(pango_layout_get_line_count(layout_.get()) >= static_cast<int>(line)) {
if(static_cast<int>(line) >= pango_layout_get_line_count(layout_.get())) {
return point(0, 0);
}
@ -222,7 +245,7 @@ point pango_text::get_cursor_position(const unsigned column, const unsigned line
if(i + 1 == column) {
break;
}
// We are beyond data.
// Beyond data.
return point(0, 0);
}
}
@ -337,19 +360,40 @@ bool pango_text::set_text(const std::string& text, const bool markedup)
<< " text '" << text
<< "' contains invalid utf-8, trimmed the invalid parts.";
}
if (highlight_start_offset_ != highlight_end_offset_) {
/** Highlight */
PangoAttrList *attribute_list = pango_attr_list_new();
int col_r = highlight_color_.r / 255.0 * 65535.0;
int col_g = highlight_color_.g / 255.0 * 65535.0;
int col_b = highlight_color_.b / 255.0 * 65535.0;
DBG_GUI_D << "highlight start : " << highlight_start_offset_ << "end : " << highlight_end_offset_;
DBG_GUI_D << "highlight rgb : " << col_r << "," << col_g << "," << col_b;
PangoAttribute *attr = pango_attr_background_new(col_r, col_g, col_b);
attr->start_index = highlight_start_offset_;
attr->end_index = highlight_end_offset_;
pango_attr_list_insert(attribute_list, attr);
pango_layout_set_attributes(layout_.get(), attribute_list);
}
if(markedup) {
if(!this->set_markup(narrow, *layout_)) {
return false;
}
} else {
/*
* pango_layout_set_text after pango_layout_set_markup might
* leave the layout in an undefined state regarding markup so
* clear it unconditionally.
*/
pango_layout_set_attributes(layout_.get(), nullptr);
if (highlight_start_offset_ == highlight_end_offset_) {
/*
* pango_layout_set_text after pango_layout_set_markup might
* leave the layout in an undefined state regarding markup so
* clear it unconditionally.
*/
pango_layout_set_attributes(layout_.get(), nullptr);
}
pango_layout_set_text(layout_.get(), narrow.c_str(), narrow.size());
}
text_ = narrow;
length_ = wide.size();
markedup_text_ = markedup;
@ -994,6 +1038,9 @@ std::size_t hash<font::pango_text>::operator()(const font::pango_text& t) const
boost::hash_combine(hash, t.alignment_);
boost::hash_combine(hash, t.ellipse_mode_);
boost::hash_combine(hash, t.add_outline_);
boost::hash_combine(hash, t.highlight_start_offset_);
boost::hash_combine(hash, t.highlight_end_offset_);
boost::hash_combine(hash, t.highlight_color_.to_rgba_bytes());
return hash;
}

View file

@ -24,6 +24,8 @@
#include <pango/pango.h>
#include <pango/pangocairo.h>
#include <iostream>
#include <functional>
#include <memory>
#include <string>
@ -172,6 +174,18 @@ public:
point get_cursor_position(
const unsigned column, const unsigned line = 0) const;
/**
* Gets the correct number of columns to move the cursor
* from Pango. Needed in case the text contains multibyte
* characters. Return value == column if the text has no
* multibyte characters.
*
* @param column The column offset of the cursor.
*
* @returns Corrected column offset.
*/
int get_byte_offset(const unsigned column) const;
/**
* Get maximum length.
*
@ -224,6 +238,14 @@ public:
*/
std::vector<std::string> get_lines() const;
/**
* Get number of lines in the text.
*
* @returns The number of lines in the text.
*
*/
unsigned get_lines_count() const { return pango_layout_get_line_count(layout_.get()); };
/**
* Gets the length of the text in bytes.
*
@ -278,6 +300,21 @@ public:
pango_text& set_add_outline(bool do_add);
/**
* Mark a specific portion of text for highlighting. Used for selection box.
* BGColor is set in set_text(), this just marks the area to be colored.
* Markup not used because the user may enter their own markup or special characters
* @param start_offset Column offset of the cursor where selection/highlight starts
* @param end_offset Column offset of the cursor where selection/highlight ends
* @param color Highlight color
*/
void set_highlight_area(const unsigned start_offset, const unsigned end_offset, const color_t& color)
{
highlight_start_offset_ = start_offset;
highlight_end_offset_ = end_offset;
highlight_color_ = color;
}
private:
/***** ***** ***** ***** Pango variables ***** ***** ***** *****/
@ -375,6 +412,10 @@ private:
/** Length of the text. */
mutable std::size_t length_;
unsigned highlight_start_offset_;
unsigned highlight_end_offset_;
color_t highlight_color_;
/** The pixel scale, used to render high-DPI text. */
int pixel_scale_;
@ -488,6 +529,10 @@ pango_text& get_text_renderer();
*/
int get_max_height(unsigned size, font::family_class fclass = font::FONT_SANS_SERIF, pango_text::FONT_STYLE style = pango_text::STYLE_NORMAL);
/* Returns the default line spacing factor
* For now hardcoded here */
constexpr float get_line_spacing_factor() { return 1.3f; };
} // namespace font
// Specialize std::hash for pango_text

View file

@ -409,6 +409,9 @@ text_shape::text_shape(const config& cfg)
, maximum_width_(cfg["maximum_width"], -1)
, characters_per_line_(cfg["text_characters_per_line"])
, maximum_height_(cfg["maximum_height"], -1)
, highlight_start_(cfg["highlight_start"], 0)
, highlight_end_(cfg["highlight_end"], 0)
, highlight_color_(cfg["highlight_color"], color_t::from_hex_string("215380"))
{
if(!font_size_.has_formula()) {
VALIDATE(font_size_(), _("Text has a font size of 0."));
@ -436,6 +439,9 @@ void text_shape::draw(wfl::map_formula_callable& variables)
font::pango_text& text_renderer = font::get_text_renderer();
text_renderer.set_highlight_area(highlight_start_(variables), highlight_end_(variables), highlight_color_(variables));
text_renderer
.set_link_aware(link_aware_(variables))
.set_link_color(link_color_(variables))

View file

@ -447,6 +447,12 @@ private:
/** The maximum height for the text. */
typed_formula<int> maximum_height_;
/** Start and end offsets for highlight */
typed_formula<int> highlight_start_;
typed_formula<int> highlight_end_;
/** The color to be used for highlighting */
typed_formula<color_t> highlight_color_;
};
}

View file

@ -0,0 +1,514 @@
/*
Copyright (C) 2023 - 2024
by babaissarkar(Subhraman Sarkar) <suvrax@gmail.com>
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/widgets/multiline_text.hpp"
#include "color.hpp"
#include "gui/core/log.hpp"
#include "gui/core/register_widget.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
#include "preferences/game.hpp"
#include "serialization/unicode.hpp"
#include "font/text.hpp"
#include "wml_exception.hpp"
#include "gettext.hpp"
#include <functional>
#include <iostream>
#define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
#define LOG_HEADER LOG_SCOPE_HEADER + ':'
namespace gui2
{
// ------------ WIDGET -----------{
REGISTER_WIDGET(multiline_text)
multiline_text::multiline_text(const implementation::builder_styled_widget& builder)
: text_box_base(builder, type())
, history_()
, max_input_length_(0)
, text_x_offset_(0)
, text_y_offset_(0)
, text_height_(0)
, dragging_(false)
, line_num_(0)
{
set_wants_mouse_left_double_click();
connect_signal<event::MOUSE_MOTION>(std::bind(
&multiline_text::signal_handler_mouse_motion, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
connect_signal<event::LEFT_BUTTON_DOWN>(std::bind(
&multiline_text::signal_handler_left_button_down, this, std::placeholders::_2, std::placeholders::_3));
connect_signal<event::LEFT_BUTTON_UP>(std::bind(
&multiline_text::signal_handler_left_button_up, this, std::placeholders::_2, std::placeholders::_3));
connect_signal<event::LEFT_BUTTON_DOUBLE_CLICK>(std::bind(
&multiline_text::signal_handler_left_button_double_click, this, std::placeholders::_2, std::placeholders::_3));
const auto conf = cast_config_to<multiline_text_definition>();
assert(conf);
set_font_size(get_text_font_size());
set_font_style(conf->text_font_style);
update_offsets();
}
void multiline_text::place(const point& origin, const point& size)
{
// Inherited.
styled_widget::place(origin, size);
set_maximum_width(get_text_maximum_width());
set_maximum_height(get_text_maximum_height(), false);
set_maximum_length(max_input_length_);
update_offsets();
}
void multiline_text::update_canvas()
{
/***** Gather the info *****/
// Set the cursor info.
const unsigned start = get_selection_start();
const int length = static_cast<int>(get_selection_length());
// Set the cursor info.
const unsigned edit_start = get_composition_start();
const int edit_length = get_composition_length();
set_maximum_length(max_input_length_);
// Set the composition info
unsigned comp_start_offset = 0;
unsigned comp_end_offset = 0;
if(edit_length == 0) {
// No nothing.
} else if(edit_length > 0) {
comp_start_offset = get_cursor_position(edit_start).x;
comp_end_offset = get_cursor_position(edit_start + edit_length).x;
} else {
comp_start_offset = get_cursor_position(edit_start + edit_length).x;
comp_end_offset = get_cursor_position(edit_start).x;
}
set_line_num_from_offset();
/***** Set in all canvases *****/
const int max_width = get_text_maximum_width();
const int max_height = get_text_maximum_height();
for(auto & tmp : get_canvases())
{
tmp.set_variable("text", wfl::variant(get_value()));
tmp.set_variable("text_x_offset", wfl::variant(text_x_offset_));
tmp.set_variable("text_y_offset", wfl::variant(text_y_offset_));
tmp.set_variable("text_maximum_width", wfl::variant(max_width));
tmp.set_variable("text_maximum_height", wfl::variant(max_height));
tmp.set_variable("text_wrap_mode", wfl::variant(PANGO_ELLIPSIZE_NONE));
tmp.set_variable("editable", wfl::variant(is_editable()));
if (length < 0) {
tmp.set_variable("highlight_start", wfl::variant(get_byte_offset(start+length)));
tmp.set_variable("highlight_end", wfl::variant(get_byte_offset(start)));
} else {
tmp.set_variable("highlight_start", wfl::variant(get_byte_offset(start)));
tmp.set_variable("highlight_end", wfl::variant(get_byte_offset(start+length)));
}
tmp.set_variable("cursor_offset_x",
wfl::variant(get_cursor_position(start + length).x));
tmp.set_variable("cursor_offset_y",
wfl::variant(get_cursor_position(start + length).y));
tmp.set_variable("composition_offset", wfl::variant(comp_start_offset));
tmp.set_variable("composition_width", wfl::variant(comp_end_offset - comp_start_offset));
tmp.set_variable("hint_text", wfl::variant(hint_text_));
tmp.set_variable("hint_image", wfl::variant(hint_image_));
}
}
void multiline_text::delete_char(const bool before_cursor)
{
if(!is_editable()) {
return;
}
if(before_cursor) {
set_cursor(get_selection_start() - 1, false);
}
set_selection_length(1);
delete_selection();
}
void multiline_text::delete_selection()
{
if(get_selection_length() == 0 || (!is_editable()) ) {
return;
}
// If we have a negative range change it to a positive range.
// This makes the rest of the algorithms easier.
int len = get_selection_length();
unsigned start = get_selection_start();
if(len < 0) {
len = -len;
start -= len;
}
std::string tmp = get_value();
set_value(utf8::erase(tmp, start, len));
set_cursor(start, false);
update_layout();
}
void multiline_text::handle_mouse_selection(point mouse, const bool start_selection)
{
mouse.x -= get_x();
mouse.y -= get_y();
// FIXME we don't test for overflow in width
if(mouse.x < static_cast<int>(text_x_offset_)
|| mouse.y < static_cast<int>(text_y_offset_)
|| mouse.y >= static_cast<int>(text_y_offset_ + get_lines_count() * font::get_line_spacing_factor() * text_height_)) {
return;
}
point cursor_pos = get_column_line(point(mouse.x - text_x_offset_, mouse.y - text_y_offset_));
int offset = cursor_pos.x;
int line = cursor_pos.y;
if(offset < 0) {
return;
}
offset += get_line_start_offset(line);
line_num_ = get_line_num_from_offset(offset);
// moving scrollbars during click causes viewport to jump
set_cursor(offset, !start_selection, false);
update_canvas();
queue_redraw();
dragging_ |= start_selection;
}
unsigned multiline_text::get_line_end_offset(unsigned line_no) {
// Should be cached if needed
std::string line = get_lines().at(line_no);
// Get correct number of characters to move for multibyte utf8 string.
int line_size = utf8::size(line);
return get_line_start_offset(line_no) + line_size;
}
unsigned multiline_text::get_line_start_offset(unsigned line_no) {
if (line_no > 0) {
return get_line_end_offset(line_no-1) + 1;
} else {
return 0;
}
}
unsigned multiline_text::get_line_num_from_offset(unsigned offset) {
unsigned line_start = 0, line_end = 0, line_no = 0;
for(unsigned i = 0; i < get_lines_count(); i++) {
line_start = get_line_start_offset(i);
line_end = get_line_end_offset(i);
if ((offset >= line_start) && (offset <= line_end)) {
line_no = i;
break;
}
}
return line_no;
}
void multiline_text::set_line_num_from_offset()
{
line_num_ = get_line_num_from_offset(get_selection_start());
}
void multiline_text::update_offsets()
{
const auto conf = cast_config_to<multiline_text_definition>();
assert(conf);
text_height_ = font::get_max_height(get_text_font_size(), get_font_family());
wfl::map_formula_callable variables;
variables.add("height", wfl::variant(get_height()));
variables.add("width", wfl::variant(get_width()));
variables.add("text_font_height", wfl::variant(text_height_));
text_x_offset_ = conf->text_x_offset(variables);
text_y_offset_ = conf->text_y_offset(variables);
// Since this variable doesn't change set it here instead of in
// update_canvas().
for(auto & tmp : get_canvases())
{
tmp.set_variable("text_font_height", wfl::variant(text_height_));
}
// Force an update of the canvas since now text_font_height is known.
update_canvas();
}
bool multiline_text::history_up()
{
if(!history_.get_enabled()) {
return false;
}
const std::string str = history_.up(get_value());
if(!str.empty()) {
set_value(str);
}
return true;
}
bool multiline_text::history_down()
{
if(!history_.get_enabled()) {
return false;
}
const std::string str = history_.down(get_value());
if(!str.empty()) {
set_value(str);
}
return true;
}
void multiline_text::handle_key_tab(SDL_Keymod modifier, bool& handled)
{
if(!is_editable())
{
return;
}
if(modifier & KMOD_CTRL) {
if(!(modifier & KMOD_SHIFT)) {
handled = history_up();
} else {
handled = history_down();
}
} else {
handled = true;
insert_char("\t");
}
}
void multiline_text::handle_key_enter(SDL_Keymod modifier, bool& handled)
{
if (is_editable() && !(modifier & (KMOD_CTRL | KMOD_ALT | KMOD_GUI))) {
insert_char("\n");
handled = true;
}
}
void multiline_text::handle_key_clear_line(SDL_Keymod /*modifier*/, bool& handled)
{
handled = true;
set_value("");
}
void multiline_text::handle_key_down_arrow(SDL_Keymod modifier, bool& handled)
{
DBG_GUI_E << LOG_SCOPE_HEADER;
handled = true;
set_line_num_from_offset();
size_t offset = get_selection_start();
if (line_num_ < get_lines_count()-1) {
offset = offset
- get_line_start_offset(line_num_)
+ get_line_start_offset(line_num_+1);
if (offset > get_line_end_offset(line_num_+1)) {
offset = get_line_end_offset(line_num_+1);
}
}
offset += get_selection_length();
if (offset <= get_length()) {
set_cursor(offset, (modifier & KMOD_SHIFT) != 0);
}
update_canvas();
queue_redraw();
}
void multiline_text::handle_key_up_arrow(SDL_Keymod modifier, bool& handled)
{
DBG_GUI_E << LOG_SCOPE_HEADER;
handled = true;
set_line_num_from_offset();
size_t offset = get_selection_start();
if (line_num_ > 0) {
offset = offset
- get_line_start_offset(line_num_)
+ get_line_start_offset(line_num_-1);
if (offset > get_line_end_offset(line_num_-1)) {
offset = get_line_end_offset(line_num_-1);
}
}
offset += get_selection_length();
/* offset is unsigned int */
if (offset <= get_length()) {
set_cursor(offset, (modifier & KMOD_SHIFT) != 0);
}
update_canvas();
queue_redraw();
}
void multiline_text::signal_handler_mouse_motion(const event::ui_event event,
bool& handled,
const point& coordinate)
{
DBG_GUI_E << get_control_type() << "[" << id() << "]: " << event << ".";
if(dragging_) {
handle_mouse_selection(coordinate, false);
}
handled = true;
}
void multiline_text::signal_handler_left_button_down(const event::ui_event event,
bool& handled)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
/*
* Copied from the base class see how we can do inheritance with the new
* system...
*/
get_window()->keyboard_capture(this);
get_window()->mouse_capture();
handle_mouse_selection(get_mouse_position(), true);
handled = true;
}
void multiline_text::signal_handler_left_button_up(const event::ui_event event,
bool& handled)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
dragging_ = false;
handled = true;
}
void
multiline_text::signal_handler_left_button_double_click(const event::ui_event event,
bool& handled)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
select_all();
handled = true;
}
// }---------- DEFINITION ---------{
multiline_text_definition::multiline_text_definition(const config& cfg)
: styled_widget_definition(cfg)
{
DBG_GUI_P << "Parsing multiline_text " << id;
load_resolutions<resolution>(cfg);
}
multiline_text_definition::resolution::resolution(const config& cfg)
: resolution_definition(cfg)
, text_x_offset(cfg["text_x_offset"])
, text_y_offset(cfg["text_y_offset"])
{
// Note the order should be the same as the enum state_t in multiline_text.hpp.
state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", _("Missing required state for editable text box")));
state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", _("Missing required state for editable text box")));
state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_focused", _("Missing required state for editable text box")));
state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_hovered", _("Missing required state for editable text box")));
}
// }---------- BUILDER -----------{
namespace implementation
{
builder_multiline_text::builder_multiline_text(const config& cfg)
: builder_styled_widget(cfg)
, history(cfg["history"])
, max_input_length(cfg["max_input_length"])
, hint_text(cfg["hint_text"].t_str())
, hint_image(cfg["hint_image"])
, editable(cfg["editable"].to_bool(true))
, wrap(cfg["wrap"].to_bool(true))
{
}
std::unique_ptr<widget> builder_multiline_text::build() const
{
auto widget = std::make_unique<multiline_text>(*this);
widget->set_editable(editable);
// A textbox doesn't have a label but a text
widget->set_value(label_string);
if(!history.empty()) {
widget->set_history(history);
}
widget->set_max_input_length(max_input_length);
widget->set_hint_data(hint_text, hint_image);
DBG_GUI_G << "Window builder: placed text box '" << id
<< "' with definition '" << definition << "'.";
return widget;
}
} // namespace implementation
// }------------ END --------------
} // namespace gui2

View file

@ -0,0 +1,392 @@
/*
Copyright (C) 2023 - 2024
by babaissarkar(Subhraman Sarkar) <suvrax@gmail.com>
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include "gui/widgets/text_box_base.hpp"
#include "gui/widgets/text_box.hpp"
#include "gui/widgets/window.hpp"
#include "gui/widgets/scrollbar_container.hpp"
namespace gui2
{
namespace implementation
{
struct builder_multiline_text;
}
// ------------ WIDGET -----------{
/**
* @ingroup GUIWidgetWML
*
* Base class for a multiline text area.
*
* The resolution for a text box also contains the following keys:
* Key |Type |Default |Description
* -------------|----------------------------------------|---------|-----------
* text_x_offset| @ref guivartype_f_unsigned "f_unsigned"|"" |The x offset of the text in the text box. This is needed for the code to determine where in the text the mouse clicks, so it can set the cursor properly.
* text_y_offset| @ref guivartype_f_unsigned "f_unsigned"|"" |The y offset of the text in the text box.
* The following states exist:
* * state_enabled - the text box is enabled.
* * state_disabled - the text box is disabled.
* * state_focussed - the text box has the focus of the keyboard.
* The following variables exist:
* Key |Type |Default |Description
* -------------|------------------------------------|---------|-----------
* label | @ref guivartype_t_string "t_string"|"" |The initial text of the text box.
* history | @ref guivartype_string "string" |"" |The name of the history for the text box. A history saves the data entered in a text box between the games. With the up and down arrow it can be accessed. To create a new history item just add a new unique name for this field and the engine will handle the rest.
* editable | @ref guivartype_bool "bool" |"true" |If the contents of the text box can be edited.
*/
class multiline_text : public text_box_base
{
friend struct implementation::builder_multiline_text;
public:
explicit multiline_text(const implementation::builder_styled_widget& builder);
/** See @ref widget::can_wrap. */
bool can_wrap() const override
{
return false;
}
/** Saves the text in the widget to the history. */
void save_to_history()
{
history_.push(get_value());
}
/***** ***** ***** setters / getters for members ***** ****** *****/
void set_history(const std::string& id)
{
history_ = text_history::get_history(id, true);
}
void set_max_input_length(const std::size_t length)
{
max_input_length_ = length;
}
void set_hint_data(const std::string& text, const std::string& image)
{
hint_text_ = text;
hint_image_ = image;
update_canvas();
}
void clear()
{
set_value("");
}
unsigned get_line_no()
{
set_line_num_from_offset();
return line_num_;
}
point get_cursor_pos()
{
return get_cursor_position(get_selection_start());
}
int get_line_end_pos()
{
return get_cursor_position(get_line_end_offset(line_num_)).x;
}
point get_text_end_pos()
{
return get_cursor_position(get_length());
}
protected:
/***** ***** ***** ***** layout functions ***** ***** ***** *****/
/** See @ref widget::place. */
virtual void place(const point& origin, const point& size) override;
/***** ***** ***** ***** Inherited ***** ***** ***** *****/
/** See @ref styled_widget::update_canvas. */
virtual void update_canvas() override;
/** Inherited from text_box_base. */
void insert_char(const std::string& unicode) override
{
text_box_base::insert_char(unicode);
update_layout();
}
/** Inherited from text_box_base. */
void set_cursor(const std::size_t offset, const bool select, const bool autoscroll = true)
{
text_box_base::set_cursor(offset, select);
set_line_num_from_offset();
if (autoscroll) {
// Whenever cursor moves, this tells scroll_text to update the scrollbars
update_layout();
}
}
/** Inherited from text_box_base. */
void goto_end_of_line(const bool select = false) override
{
set_line_num_from_offset();
set_cursor(get_line_end_offset(line_num_), select);
}
/** Inherited from text_box_base. */
void goto_start_of_line(const bool select = false) override
{
set_line_num_from_offset();
set_cursor(get_line_start_offset(line_num_), select);
}
/** Inherited from text_box_base. */
void goto_end_of_data(const bool select = false) override
{
text_box_base::goto_end_of_data(select);
update_layout();
}
/** Inherited from text_box_base. */
void goto_start_of_data(const bool select = false) override
{
text_box_base::goto_start_of_data(select);
update_layout();
}
/** Inherited from text_box_base. */
void paste_selection(const bool mouse) override
{
text_box_base::paste_selection(mouse);
update_layout();
}
/** Inherited from text_box_base. */
void delete_char(const bool before_cursor) override;
/** Inherited from text_box_base. */
void delete_selection() override;
void handle_mouse_selection(point mouse, const bool start_selection);
private:
/** The history text for this widget. */
text_history history_;
/** The maximum length of the text input. */
std::size_t max_input_length_;
/**
* The x offset in the widget where the text starts.
*
* This value is needed to translate a location in the widget to a location
* in the text.
*/
unsigned text_x_offset_;
/**
* The y offset in the widget where the text starts.
*
* Needed to determine whether a click is on the text.
*/
unsigned text_y_offset_;
/**
* The height of the text itself.
*
* Needed to determine whether a click is on the text.
*/
unsigned text_height_;
/** Updates text_x_offset_ and text_y_offset_. */
void update_offsets();
/** Is the mouse in dragging mode, this affects selection in mouse move */
bool dragging_;
/** Helper text to display (such as "Search") if the text box is empty. */
std::string hint_text_;
/** Image (such as a magnifying glass) that accompanies the help text. */
std::string hint_image_;
/** Line number of text */
unsigned line_num_;
/** utility function to calculate and set line_num_ from offset */
void set_line_num_from_offset();
unsigned get_line_num_from_offset(unsigned offset);
/** Utility function to calculate the offset of the end of the line
*/
unsigned get_line_end_offset(unsigned line_no);
/** Utility function to calculate the offset of the end of the line
*/
unsigned get_line_start_offset(unsigned line_no);
/** Update layout. To be called when text size changes */
void update_layout() {
set_label(get_value());
get_window()->invalidate_layout();
}
/**
* Inherited from text_box_base.
*
* Unmodified Handled.
* Control Ignored.
* Shift Ignored.
* Alt Ignored.
*/
void handle_key_up_arrow(SDL_Keymod /*modifier*/, bool& handled) override;
/**
* Inherited from text_box_base.
*
* Unmodified Handled.
* Control Ignored.
* Shift Ignored.
* Alt Ignored.
*/
void handle_key_down_arrow(SDL_Keymod /*modifier*/, bool& handled) override;
/**
* Inherited from text_box_base.
*
* Unmodified Handled.
* Control Ignored.
* Shift Ignored.
* Alt Ignored.
*/
void handle_key_left_arrow(SDL_Keymod modifier, bool& handled) override
{
text_box_base::handle_key_left_arrow(modifier, handled);
update_layout();
}
/**
* Inherited from text_box_base.
*
* Unmodified Handled.
* Control Ignored.
* Shift Ignored.
* Alt Ignored.
*/
void handle_key_right_arrow(SDL_Keymod modifier, bool& handled) override
{
text_box_base::handle_key_right_arrow(modifier, handled);
update_layout();
}
/**
* Goes one item up in the history.
*
* @returns True if there's a history, false otherwise.
*/
bool history_up();
/**
* Goes one item down in the history.
*
* @returns True if there's a history, false otherwise.
*/
bool history_down();
/** Inherited from text_box_base. */
void handle_key_tab(SDL_Keymod modifier, bool& handled) override;
/** Inherited from text_box_base. */
void handle_key_enter(SDL_Keymod modifier, bool& handled) override;
/** Inherited from text_box_base. */
void handle_key_clear_line(SDL_Keymod modifier, bool& handled) override;
public:
/** Static type getter that does not rely on the widget being constructed. */
static const std::string& type();
private:
/** Inherited from styled_widget, implemented by REGISTER_WIDGET. */
virtual const std::string& get_control_type() const override;
/***** ***** ***** signal handlers ***** ****** *****/
void signal_handler_mouse_motion(const event::ui_event event,
bool& handled,
const point& coordinate);
void signal_handler_left_button_down(const event::ui_event event,
bool& handled);
void signal_handler_left_button_up(const event::ui_event event,
bool& handled);
void signal_handler_left_button_double_click(const event::ui_event event,
bool& handled);
};
// }---------- DEFINITION ---------{
struct multiline_text_definition : public styled_widget_definition
{
explicit multiline_text_definition(const config& cfg);
struct resolution : public resolution_definition
{
explicit resolution(const config& cfg);
typed_formula<unsigned> text_x_offset;
typed_formula<unsigned> text_y_offset;
};
};
// }---------- BUILDER -----------{
namespace implementation
{
struct builder_multiline_text : public builder_styled_widget
{
public:
explicit builder_multiline_text(const config& cfg);
using builder_styled_widget::build;
virtual std::unique_ptr<widget> build() const override;
std::string history;
std::size_t max_input_length;
t_string hint_text;
std::string hint_image;
bool editable;
bool wrap;
};
} // namespace implementation
// }------------ END --------------
} // namespace gui2

View file

@ -0,0 +1,251 @@
/*
Copyright (C) 2023 - 2024
by babaissarkar(Subhraman Sarkar) <suvrax@gmail.com>
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/widgets/scroll_text.hpp"
#include "gui/widgets/multiline_text.hpp"
#include "gui/auxiliary/find_widget.hpp"
#include "gui/core/log.hpp"
#include "gui/core/window_builder/helper.hpp"
#include "gui/core/register_widget.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/scrollbar.hpp"
#include "gui/widgets/spacer.hpp"
#include "gui/widgets/window.hpp"
#include "gettext.hpp"
#include "wml_exception.hpp"
#include <functional>
#include <iostream>
#define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
#define LOG_HEADER LOG_SCOPE_HEADER + ':'
namespace gui2
{
// ------------ WIDGET -----------{
REGISTER_WIDGET(scroll_text)
scroll_text::scroll_text(const implementation::builder_scroll_text& builder)
: scrollbar_container(builder, type())
, state_(ENABLED)
, wrap_on_(false)
, text_alignment_(builder.text_alignment)
{
connect_signal<event::LEFT_BUTTON_DOWN>(
std::bind(&scroll_text::signal_handler_left_button_down, this, std::placeholders::_2),
event::dispatcher::back_pre_child);
}
multiline_text* scroll_text::get_internal_text_box()
{
if(content_grid()) {
return dynamic_cast<multiline_text*>(content_grid()->find("_text", false));
}
return nullptr;
}
void scroll_text::set_label(const t_string& label)
{
if(multiline_text* widget = get_internal_text_box()) {
widget->set_value(label);
widget->set_label(label);
bool resize_needed = !content_resize_request();
if(resize_needed && get_size() != point()) {
place(get_origin(), get_size());
}
}
}
std::string scroll_text::get_value()
{
if(multiline_text* widget = get_internal_text_box()) {
return widget->get_value();
} else {
return "";
}
}
void scroll_text::set_text_alignment(const PangoAlignment text_alignment)
{
// Inherit.
styled_widget::set_text_alignment(text_alignment);
text_alignment_ = text_alignment;
if(multiline_text* widget = get_internal_text_box()) {
widget->set_text_alignment(text_alignment_);
}
}
void scroll_text::set_use_markup(bool use_markup)
{
// Inherit.
styled_widget::set_use_markup(use_markup);
if(multiline_text* widget = get_internal_text_box()) {
widget->set_use_markup(use_markup);
}
}
void scroll_text::set_self_active(const bool active)
{
state_ = active ? ENABLED : DISABLED;
}
bool scroll_text::get_active() const
{
return state_ != DISABLED;
}
unsigned scroll_text::get_state() const
{
return state_;
}
void scroll_text::finalize_subclass()
{
multiline_text* text = get_internal_text_box();
assert(text);
text->set_editable(is_editable());
text->set_label(get_label());
text->set_text_alignment(text_alignment_);
text->set_use_markup(get_use_markup());
}
void scroll_text::place(const point& origin, const point& size) {
scrollbar_container::place(origin, size);
if(multiline_text* widget = get_internal_text_box()) {
const SDL_Rect& visible_area = content_visible_area();
if (widget->get_cursor_pos().x < visible_area.w/2.0) {
scroll_horizontal_scrollbar(scrollbar_base::BEGIN);
} else {
scroll_horizontal_scrollbar_by(widget->get_cursor_pos().x - visible_area.w/2.0);
}
if (widget->get_cursor_pos().y >= (widget->get_text_end_pos().y - visible_area.h/2.0)) {
if (widget->get_lines_count() > 1) {
scroll_vertical_scrollbar(scrollbar_base::END);
} else {
scroll_vertical_scrollbar(scrollbar_base::BEGIN);
}
} else if (widget->get_cursor_pos().y < visible_area.h/2.0) {
scroll_vertical_scrollbar(scrollbar_base::BEGIN);
} else {
scroll_vertical_scrollbar_by(widget->get_cursor_pos().y - visible_area.h/2.0);
}
if (widget->get_length() == 0) {
scroll_horizontal_scrollbar(scrollbar_base::BEGIN);
scroll_vertical_scrollbar(scrollbar_base::BEGIN);
}
}
}
void scroll_text::set_can_wrap(bool can_wrap)
{
wrap_on_ = can_wrap;
}
bool scroll_text::can_wrap() const
{
return true;
}
void scroll_text::signal_handler_left_button_down(const event::ui_event event)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
get_window()->keyboard_capture(this);
}
// }---------- DEFINITION ---------{
scroll_text_definition::scroll_text_definition(const config& cfg)
: styled_widget_definition(cfg)
{
DBG_GUI_P << "Parsing scroll label " << id;
load_resolutions<resolution>(cfg);
}
scroll_text_definition::resolution::resolution(const config& cfg)
: resolution_definition(cfg), grid(nullptr)
{
// Note the order should be the same as the enum state_t is scroll_text.hpp.
state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", _("Missing required state for scroll label control")));
state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", _("Missing required state for scroll label control")));
auto child = VALIDATE_WML_CHILD(cfg, "grid", _("No grid defined for scroll label control"));
grid = std::make_shared<builder_grid>(child);
}
// }---------- BUILDER -----------{
namespace implementation
{
builder_scroll_text::builder_scroll_text(const config& cfg)
: implementation::builder_styled_widget(cfg)
, vertical_scrollbar_mode(get_scrollbar_mode(cfg["vertical_scrollbar_mode"]))
, horizontal_scrollbar_mode(get_scrollbar_mode(cfg["horizontal_scrollbar_mode"]))
, text_alignment(decode_text_alignment(cfg["text_alignment"]))
, editable(cfg["editable"].to_bool(true))
{
/** Horizontal scrollbar default to auto. AUTO_VISIBLE_FIRST_RUN doesn't work. */
if (horizontal_scrollbar_mode == scrollbar_container::AUTO_VISIBLE_FIRST_RUN) {
horizontal_scrollbar_mode = scrollbar_container::AUTO_VISIBLE;
}
if (vertical_scrollbar_mode == scrollbar_container::AUTO_VISIBLE_FIRST_RUN) {
vertical_scrollbar_mode = scrollbar_container::AUTO_VISIBLE;
}
}
std::unique_ptr<widget> builder_scroll_text::build() const
{
auto widget = std::make_unique<scroll_text>(*this);
widget->set_vertical_scrollbar_mode(vertical_scrollbar_mode);
widget->set_horizontal_scrollbar_mode(horizontal_scrollbar_mode);
widget->set_editable(editable);
widget->set_text_alignment(text_alignment);
const auto conf = widget->cast_config_to<scroll_text_definition>();
assert(conf);
widget->init_grid(*conf->grid);
widget->finalize_setup();
DBG_GUI_G << "Window builder: placed scroll label '" << id
<< "' with definition '" << definition << "'.";
return widget;
}
} // namespace implementation
// }------------ END --------------
} // namespace gui2

View file

@ -0,0 +1,220 @@
/*
Copyright (C) 2023 - 2024
by babaissarkar(Subhraman Sarkar) <suvrax@gmail.com>
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include "gui/widgets/scrollbar_container.hpp"
#include "gui/core/widget_definition.hpp"
#include "gui/core/window_builder.hpp"
#include "gui/widgets/multiline_text.hpp"
namespace gui2
{
// ------------ WIDGET -----------{
class label;
class spacer;
namespace implementation
{
struct builder_scroll_text;
}
/**
* @ingroup GUIWidgetWML
*
* Scrollable text area
*
* A multiline text area that shows a scrollbar if the text gets too long.
*
* Key |Type |Default |Description
* -------------|----------------------------|---------|-----------
* grid | @ref guivartype_grid "grid"|mandatory|A grid containing the widgets for main widget.
*
* TODO: we need one definition for a vertical scrollbar since this is the second time we use it.
*
* ID (return value)|Type |Default |Description
* -----------------|----------------------------|---------|-----------
* _content_grid | @ref guivartype_grid "grid"|mandatory|A grid which should only contain one multiline_text widget.
* _scrollbar_grid | @ref guivartype_grid "grid"|mandatory|A grid for the scrollbar (Merge with listbox info.)
*
* Description of necessary widgets contained inside _content_grid :
*
* ID (return value)|Type |Default |Description
* -----------------|--------------------------------|---------|-----------
* _text | @ref gui2::text_box |mandatory|The text_box that shows the value.
* The following states exist:
* * state_enabled - the scroll text is enabled.
* * state_disabled - the scroll text is disabled.
* List with the scrollbar container specific variables:
* Key |Type |Default |Description
* -------------------------|------------------------------------------------|------------|-----------
* vertical_scrollbar_mode | @ref guivartype_scrollbar_mode "scrollbar_mode"|initial_auto|Determines whether or not to show the scrollbar.
* horizontal_scrollbar_mode| @ref guivartype_scrollbar_mode "scrollbar_mode"|initial_auto|Determines whether or not to show the scrollbar.
* editable | @ref guivartype_bool "bool" |"true" |If the contents of included multiline_text can be edited.
*/
class scroll_text : public scrollbar_container
{
friend struct implementation::builder_scroll_text;
public:
explicit scroll_text(const implementation::builder_scroll_text& builder);
// /** See @ref styled_widget::set_label. */
virtual void set_label(const t_string& label) override;
// void set_value(const std::string text);
void set_value(const t_string& label) {
set_label(label);
}
std::string get_value();
/** See @ref styled_widget::set_text_alignment. */
virtual void set_text_alignment(const PangoAlignment text_alignment) override;
/** See @ref styled_widget::set_use_markup. */
virtual void set_use_markup(bool use_markup) override;
/** See @ref container_base::set_self_active. */
virtual void set_self_active(const bool active) override;
/***** ***** ***** setters / getters for members ***** ****** *****/
/** See @ref styled_widget::get_active. */
virtual bool get_active() const override;
/** See @ref styled_widget::get_state. */
virtual unsigned get_state() const override;
bool can_wrap() const override;
void set_can_wrap(bool can_wrap);
void set_editable(bool editable)
{
editable_ = editable;
}
bool is_editable()
{
return editable_;
}
private:
/**
* Possible states of the widget.
*
* Note the order of the states must be the same as defined in settings.hpp.
*/
enum state_t {
ENABLED,
DISABLED,
};
// It's not needed for now so keep it disabled, no definition exists yet.
// void set_state(const state_t state);
/**
* Current state of the widget.
*
* The state of the widget determines what to render and how the widget
* reacts to certain 'events'.
*/
state_t state_;
bool wrap_on_;
PangoAlignment text_alignment_;
bool editable_;
void finalize_subclass() override;
/** Used for moving scrollbars.
Has to be called from signal notify_modified, otherwise
doesn't work after invalidate_layout. */
void refresh();
unsigned get_horizontal_position() {
assert(horizontal_scrollbar());
return horizontal_scrollbar()->get_positioner_offset();
}
unsigned get_vertical_position() {
assert(vertical_scrollbar());
return vertical_scrollbar()->get_positioner_offset();
}
void place(const point& origin, const point& size);
public:
/** Static type getter that does not rely on the widget being constructed. */
static const std::string& type();
multiline_text* get_internal_text_box();
private:
/***** ***** ***** inherited ****** *****/
/** Inherited from styled_widget, implemented by REGISTER_WIDGET. */
virtual const std::string& get_control_type() const override;
/***** ***** ***** signal handlers ***** ****** *****/
void signal_handler_left_button_down(const event::ui_event event);
};
// }---------- DEFINITION ---------{
struct scroll_text_definition : public styled_widget_definition
{
explicit scroll_text_definition(const config& cfg);
struct resolution : public resolution_definition
{
explicit resolution(const config& cfg);
builder_grid_ptr grid;
};
};
// }---------- BUILDER -----------{
namespace implementation
{
struct builder_scroll_text : public builder_styled_widget
{
explicit builder_scroll_text(const config& cfg);
using builder_styled_widget::build;
virtual std::unique_ptr<widget> build() const override;
scrollbar_container::scrollbar_mode vertical_scrollbar_mode;
scrollbar_container::scrollbar_mode horizontal_scrollbar_mode;
const PangoAlignment text_alignment;
bool editable;
};
} // namespace implementation
// }------------ END --------------
} // namespace gui2

View file

@ -179,14 +179,14 @@ public:
return pixels_per_step_;
}
protected:
void finalize_setup();
unsigned get_positioner_offset() const
{
return positioner_offset_;
}
protected:
void finalize_setup();
unsigned get_positioner_length() const
{
return positioner_length_;

View file

@ -35,6 +35,7 @@
namespace gui2
{
namespace
{
@ -972,6 +973,22 @@ void scrollbar_container::scroll_horizontal_scrollbar(const scrollbar_base::scro
scrollbar_moved();
}
void scrollbar_container::scroll_vertical_scrollbar_by(const int pixels)
{
assert(vertical_scrollbar_);
vertical_scrollbar_->scroll_by(pixels);
move_viewport(0, pixels);
}
void scrollbar_container::scroll_horizontal_scrollbar_by(const int pixels)
{
assert(horizontal_scrollbar_);
horizontal_scrollbar_->scroll_by(pixels);
move_viewport(pixels, 0);
}
void scrollbar_container::handle_key_home(SDL_Keymod /*modifier*/, bool& handled)
{
assert(vertical_scrollbar_ && horizontal_scrollbar_);
@ -1057,7 +1074,6 @@ void scrollbar_container::handle_key_right_arrow(SDL_Keymod /*modifier*/, bool&
void scrollbar_container::scrollbar_moved()
{
// Init.
assert(content_ && content_grid_);
assert(vertical_scrollbar_ && horizontal_scrollbar_);
/*** Update the content location. ***/
@ -1069,7 +1085,15 @@ void scrollbar_container::scrollbar_moved()
? 0
: vertical_scrollbar_->get_item_position() * vertical_scrollbar_->get_step_size();
const point content_origin {content_->get_x() - x_offset, content_->get_y() - y_offset};
move_viewport(x_offset, y_offset);
}
void scrollbar_container::move_viewport(const int pixels_x, const int pixels_y)
{
// Init.
assert(content_ && content_grid_);
const point content_origin {content_->get_x() - pixels_x, content_->get_y() - pixels_y};
content_grid_->set_origin(content_origin);
content_grid_->set_visible_rectangle(content_visible_area_);

View file

@ -223,6 +223,20 @@ public:
*/
void scroll_horizontal_scrollbar(const scrollbar_base::scroll_mode scroll);
/**
* Scrolls the vertical scrollbar by pixel.
*
* @param pixels The number of pixels the bar scrolls by.
*/
void scroll_vertical_scrollbar_by(const int pixels);
/**
* Scrolls the horizontal scrollbar by pixel.
*
* @param pixels The number of pixels the bar scrolls by.
*/
void scroll_horizontal_scrollbar_by(const int pixels);
/**
* Callback when the scrollbar moves (NOTE maybe only one callback needed).
* Maybe also make protected or private and add a friend.
@ -448,6 +462,10 @@ protected:
*/
virtual void handle_key_right_arrow(SDL_Keymod modifier, bool& handled);
protected:
/** The builder needs to call us so we do our setup. */
void finalize_setup();
private:
/**
* Possible states of the widget.
@ -494,9 +512,6 @@ private:
*/
SDL_Rect content_visible_area_;
/** The builder needs to call us so we do our setup. */
void finalize_setup(); // FIXME make protected
/**
* Function for the subclasses to do their setup.
*
@ -523,10 +538,14 @@ private:
*/
virtual void set_content_size(const point& origin, const point& size);
/** Helper function which needs to be called after the scollbar moved. */
/** Helper function which needs to be called after the scollbar moves by item. */
void scrollbar_moved();
public:
/** To be called after the scollbar moves manually (by pixel) to move the viewport.
* Shifts the viewport origin pixels_x left and pixels_y right.*/
void move_viewport(const int pixels_x, const int pixels_y);
/** Static type getter that does not rely on the widget being constructed. */
static const std::string& type();

180
src/gui/widgets/spinner.cpp Normal file
View file

@ -0,0 +1,180 @@
/*
Copyright (C) 2023 - 2024
by babaissarkar(Subhraman Sarkar) <suvrax@gmail.com>
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/widgets/spinner.hpp"
#include "gui/auxiliary/find_widget.hpp"
#include "gui/widgets/repeating_button.hpp"
#include "gui/widgets/text_box.hpp"
#include "gui/core/log.hpp"
#include "gui/core/window_builder/helper.hpp"
#include "gui/core/register_widget.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
#include "gettext.hpp"
#include "wml_exception.hpp"
#include <functional>
#define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
#define LOG_HEADER LOG_SCOPE_HEADER + ':'
namespace gui2
{
// ------------ WIDGET -----------{
REGISTER_WIDGET(spinner)
spinner::spinner(const implementation::builder_spinner& builder)
: container_base(builder, type())
, state_(ENABLED)
, step_size_(1)
, invalid_(false)
{
connect_signal<event::LEFT_BUTTON_DOWN>(
std::bind(&spinner::signal_handler_left_button_down, this, std::placeholders::_2),
event::dispatcher::back_pre_child);
}
text_box* spinner::get_internal_text_box()
{
return find_widget<text_box>(this, "_text", false, true);
}
void spinner::set_value(const int val)
{
text_box* edit_area = get_internal_text_box();
if (edit_area != nullptr) {
edit_area->set_value(std::to_string(val));
}
}
int spinner::get_value()
{
/* Return 0 if invalid.
* TODO: give visual indication of wrong value
*/
int val;
try {
text_box* edit_area = get_internal_text_box();
if (edit_area != nullptr) {
val = stoi(edit_area->get_value());
invalid_ = false;
} else {
val = 0;
invalid_ = true;
}
} catch(std::invalid_argument const& /*ex*/) {
val = 0;
invalid_ = true;
} catch(std::out_of_range const& /*ex*/) {
val = 0;
invalid_ = true;
}
return val;
}
void spinner::finalize_setup()
{
repeating_button* btn_prev = find_widget<repeating_button>(this, "_prev", false, true);
repeating_button* btn_next = find_widget<repeating_button>(this, "_next", false, true);
btn_prev->connect_signal_mouse_left_down(std::bind(&spinner::prev, this));
btn_next->connect_signal_mouse_left_down(std::bind(&spinner::next, this));
}
void spinner::set_self_active(const bool active)
{
state_ = active ? ENABLED : DISABLED;
}
bool spinner::get_active() const
{
return state_ != DISABLED;
}
unsigned spinner::get_state() const
{
return state_;
}
bool spinner::can_wrap() const
{
return true;
}
void spinner::signal_handler_left_button_down(const event::ui_event event)
{
DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
get_window()->keyboard_capture(this);
}
// }---------- DEFINITION ---------{
spinner_definition::spinner_definition(const config& cfg)
: styled_widget_definition(cfg)
{
DBG_GUI_P << "Parsing spinner " << id;
load_resolutions<resolution>(cfg);
}
spinner_definition::resolution::resolution(const config& cfg)
: resolution_definition(cfg), grid(nullptr)
{
// Note the order should be the same as the enum state_t is spinner.hpp.
state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", _("Missing required state for scroll label control")));
state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", _("Missing required state for scroll label control")));
auto child = VALIDATE_WML_CHILD(cfg, "grid", _("No grid defined for scroll label control"));
grid = std::make_shared<builder_grid>(child);
}
// }---------- BUILDER -----------{
namespace implementation
{
builder_spinner::builder_spinner(const config& cfg)
: implementation::builder_styled_widget(cfg)
{
}
std::unique_ptr<widget> builder_spinner::build() const
{
auto widget = std::make_unique<spinner>(*this);
const auto conf = widget->cast_config_to<spinner_definition>();
assert(conf);
widget->init_grid(*conf->grid);
widget->finalize_setup();
DBG_GUI_G << "Window builder: placed spinner '" << id
<< "' with definition '" << definition << "'.";
return widget;
}
} // namespace implementation
// }------------ END --------------
} // namespace gui2

197
src/gui/widgets/spinner.hpp Normal file
View file

@ -0,0 +1,197 @@
/*
Copyright (C) 2023 - 2024
by babaissarkar(Subhraman Sarkar) <suvrax@gmail.com>
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#pragma once
#include "gui/widgets/container_base.hpp"
#include "gui/core/widget_definition.hpp"
#include "gui/core/window_builder.hpp"
#include "gui/widgets/text_box.hpp"
#include <iostream>
namespace gui2
{
// ------------ WIDGET -----------{
class label;
class spacer;
namespace implementation
{
struct builder_spinner;
}
/**
* @ingroup GUIWidgetWML
*
* Spinner widget.
*
* A widget with a text_box and two button (named _prev and _next) that allows user to increase
* or decrease the numeric value inside the text_box. Non-numeric values are considered as zero.
*
* Key |Type |Default |Description
* -------------|----------------------------|---------|-----------
* grid | @ref guivartype_grid "grid"|mandatory|A grid containing the widgets for main widget.
*
* TODO: we need one definition for a vertical scrollbar since this is the second time we use it.
*
* ID (return value)|Type |Default |Description
* -----------------|--------------------------------|---------|-----------
* _content_grid | @ref guivartype_grid "grid" |mandatory|A grid which should contain a text_box and two buttons.
*
* Description of necessary widgets contained inside _content_grid :
*
* ID (return value)|Type |Default |Description
* -----------------|--------------------------------|---------|-----------
* _text | @ref gui2::text_box |mandatory|The text_box that shows the value.
* _prev | @ref gui2::button |mandatory|The previous button, clicking on it decreases value by 1.
* _next | @ref gui2::button |mandatory|The next button, clicking on it increases value by 1.
* The following states exist:
* * state_enabled - the spinner is enabled.
* * state_disabled - the spinner is disabled.
*/
class spinner : public container_base
{
friend struct implementation::builder_spinner;
public:
explicit spinner(const implementation::builder_spinner& builder);
/** See @ref container_base::set_self_active. */
virtual void set_self_active(const bool active) override;
/***** ***** ***** setters / getters for members ***** ****** *****/
/** See @ref styled_widget::get_active. */
virtual bool get_active() const override;
/** See @ref styled_widget::get_state. */
virtual unsigned get_state() const override;
bool can_wrap() const override;
void set_value(const int val);
int get_value();
void prev()
{
// Allow negatives?
if (get_value() > 0) {
set_value(get_value() - step_size_);
} else {
if (invalid_) {
set_value(0);
}
}
}
void next()
{
int val = get_value();
if (!invalid_) {
// No max value
set_value(val + step_size_);
} else {
set_value(0);
}
}
private:
/**
* Possible states of the widget.
*
* Note the order of the states must be the same as defined in settings.hpp.
*/
enum state_t {
ENABLED,
DISABLED,
};
// It's not needed for now so keep it disabled, no definition exists yet.
// void set_state(const state_t state);
/**
* Current state of the widget.
*
* The state of the widget determines what to render and how the widget
* reacts to certain 'events'.
*/
state_t state_;
/** The grid that holds the content. */
std::unique_ptr<grid> content_grid_;
int step_size_;
/** If the entered data is invalid. */
bool invalid_;
text_box* get_internal_text_box();
void finalize_setup();
public:
/** Static type getter that does not rely on the widget being constructed. */
static const std::string& type();
private:
/***** ***** ***** inherited ****** *****/
/** Inherited from styled_widget, implemented by REGISTER_WIDGET. */
virtual const std::string& get_control_type() const override;
/***** ***** ***** signal handlers ***** ****** *****/
void signal_handler_left_button_down(const event::ui_event event);
};
// }---------- DEFINITION ---------{
struct spinner_definition : public styled_widget_definition
{
explicit spinner_definition(const config& cfg);
struct resolution : public resolution_definition
{
explicit resolution(const config& cfg);
builder_grid_ptr grid;
};
};
// }---------- BUILDER -----------{
namespace implementation
{
struct builder_spinner : public builder_styled_widget
{
explicit builder_spinner(const config& cfg);
using builder_styled_widget::build;
virtual std::unique_ptr<widget> build() const override;
};
} // namespace implementation
// }------------ END --------------
} // namespace gui2

View file

@ -233,7 +233,7 @@ public:
return label_;
}
virtual void set_label(const t_string& label);
virtual void set_label(const t_string& text);
virtual void set_use_markup(bool use_markup);

View file

@ -40,6 +40,7 @@ text_box_base::text_box_base(const implementation::builder_styled_widget& builde
, text_()
, selection_start_(0)
, selection_length_(0)
, editable_(true)
, ime_composing_(false)
, ime_start_point_(0)
, cursor_timer_(0)
@ -48,7 +49,7 @@ text_box_base::text_box_base(const implementation::builder_styled_widget& builde
, text_changed_callback_()
{
auto cfg = get_control(control_type, builder.definition);
text_.set_family_class(cfg->text_font_family);
set_font_family(cfg->text_font_family);
#ifdef __unix__
// pastes on UNIX systems.
@ -153,8 +154,11 @@ void text_box_base::set_cursor(const std::size_t offset, const bool select)
queue_redraw();
} else {
assert(offset <= text_.get_length());
selection_start_ = offset;
if (offset <= text_.get_length()) {
selection_start_ = offset;
} else {
selection_start_ = 0;
}
selection_length_ = 0;
update_canvas();
@ -164,6 +168,11 @@ void text_box_base::set_cursor(const std::size_t offset, const bool select)
void text_box_base::insert_char(const std::string& unicode)
{
if(!editable_)
{
return;
}
delete_selection();
if(text_.insert_text(selection_start_, unicode)) {
@ -220,6 +229,11 @@ void text_box_base::copy_selection(const bool mouse)
void text_box_base::paste_selection(const bool mouse)
{
if(!editable_)
{
return;
}
const std::string& text = desktop::clipboard::copy_from_clipboard(mouse);
if(text.empty()) {
return;
@ -584,17 +598,28 @@ void text_box_base::signal_handler_sdl_key_down(const event::ui_event event,
break;
case SDLK_BACKSPACE:
if (!is_editable())
{
return;
}
handle_key_backspace(modifier, handled);
break;
case SDLK_u:
if(!(modifier & KMOD_CTRL)) {
if( !(modifier & KMOD_CTRL) || !is_editable() ) {
return;
}
handle_key_clear_line(modifier, handled);
break;
case SDLK_DELETE:
if (!is_editable())
{
return;
}
handle_key_delete(modifier, handled);
break;
@ -610,17 +635,20 @@ void text_box_base::signal_handler_sdl_key_down(const event::ui_event event,
break;
case SDLK_x:
if(!(modifier & modifier_key)) {
if( !(modifier & modifier_key) ) {
return;
}
copy_selection(false);
delete_selection();
if ( is_editable() ) {
delete_selection();
}
handled = true;
break;
case SDLK_v:
if(!(modifier & modifier_key)) {
if( !(modifier & modifier_key) || !is_editable() ) {
return;
}
@ -630,11 +658,14 @@ void text_box_base::signal_handler_sdl_key_down(const event::ui_event event,
case SDLK_RETURN:
case SDLK_KP_ENTER:
if(!is_composing() || (modifier & (KMOD_CTRL | KMOD_ALT | KMOD_GUI | KMOD_SHIFT))) {
return;
}
// The IME will handle it, we just need to make sure nothing else handles it too.
handled = true;
// TODO: check if removing the following check causes any side effects
// To be removed if there aren't any text rendering problems.
// if(!is_composing()) {
// return;
// }
handle_key_enter(modifier, handled);
break;
case SDLK_ESCAPE:
@ -645,6 +676,10 @@ void text_box_base::signal_handler_sdl_key_down(const event::ui_event event,
handled = true;
break;
case SDLK_TAB:
handle_key_tab(modifier, handled);
break;
default:
// Don't call the text changed callback if nothing happened.
return;

View file

@ -65,11 +65,52 @@ public:
void set_maximum_length(const std::size_t maximum_length);
/**
* Wrapper function, returns length of the text in pango column offsets.
* See @ref font::pango_text::get_length.
*/
std::size_t get_length() const
{
return text_.get_length();
}
/**
* Wrapper function, returns a vector with the lines.
* See @ref font::pango_text::get_lines.
*/
std::vector<std::string> get_lines()
{
return text_.get_lines();
}
/**
* Wrapper function, return number of lines.
* See @ref font::pango_text::get_lines_count.
*/
unsigned get_lines_count() const
{
return text_.get_lines_count();
}
/**
* Wrapper function, returns corrected column offset from pango.
* See @ref font::pango_text::get_byte_offset.
*/
int get_byte_offset(const unsigned column) const
{
return text_.get_byte_offset(column);
}
/**
* Wrapper function, sets the area between column start and end
* offset to be highlighted in a specific color.
* See @ref font::pango_text::set_highlight_area.
*/
void set_highlight_area(const unsigned start_offset, const unsigned end_offset, const color_t& color)
{
text_.set_highlight_area(start_offset, end_offset, color);
}
/***** ***** ***** setters / getters for members ***** ****** *****/
/**
@ -118,6 +159,17 @@ public:
*/
void set_selection(std::size_t start, int length);
void set_editable(bool editable)
{
editable_ = editable;
update_canvas();
}
bool is_editable()
{
return editable_;
}
protected:
/** Get length of composition text by IME **/
size_t get_composition_length() const;
@ -138,7 +190,7 @@ protected:
* @param select Select the text from the original cursor
* position till the end of the data?
*/
void goto_end_of_data(const bool select = false)
virtual void goto_end_of_data(const bool select = false)
{
set_cursor(text_.get_length(), select);
}
@ -157,7 +209,7 @@ protected:
* @param select Select the text from the original cursor
* position till the beginning of the data?
*/
void goto_start_of_data(const bool select = false)
virtual void goto_start_of_data(const bool select = false)
{
set_cursor(0, select);
}
@ -176,7 +228,7 @@ protected:
* @param select Select the text from the original cursor
* position till the new position?
*/
void set_cursor(const std::size_t offset, const bool select);
virtual void set_cursor(const std::size_t offset, const bool select);
/**
* Inserts a character at the cursor.
@ -219,6 +271,17 @@ protected:
return text_.get_column_line(position);
}
font::family_class get_font_family()
{
return font_family_;
}
void set_font_family(font::family_class fclass)
{
font_family_ = fclass;
text_.set_family_class(font_family_);
}
void set_font_size(const unsigned font_size)
{
text_.set_font_size(font_size);
@ -303,6 +366,9 @@ private:
/** The text entered in the widget. */
font::pango_text text_;
/** font family */
font::family_class font_family_;
/** Cached version of the text without any pending IME modifications. */
std::string text_cached_;
@ -318,6 +384,9 @@ private:
*/
int selection_length_;
/** If this text_box_base is editable */
bool editable_;
// Values to support input method editors
bool ime_composing_;
int ime_start_point_;
@ -377,7 +446,7 @@ private:
* Alt Ignored.
*/
virtual void handle_key_clear_line(SDL_Keymod modifier, bool& handled) = 0;
protected:
/**
* Left arrow key pressed.
*
@ -400,6 +469,7 @@ private:
*/
virtual void handle_key_right_arrow(SDL_Keymod modifier, bool& handled);
private:
/**
* Home key pressed.
*
@ -483,6 +553,18 @@ private:
{
}
/**
* Enter key.
*
* Unmodified Handled by Window.
* Control Implementation defined.
* Shift Implementation defined.
* Alt Implementation defined.
*/
virtual void handle_key_enter(SDL_Keymod /*modifier*/, bool& /*handled*/)
{
}
protected:
virtual void handle_commit(bool& handled,
const std::string& unicode);

View file

@ -43,6 +43,7 @@
#include "gui/dialogs/tooltip.hpp"
#include "gui/widgets/button.hpp"
#include "gui/widgets/container_base.hpp"
#include "gui/widgets/multiline_text.hpp"
#include "gui/widgets/text_box_base.hpp"
#include "gui/core/register_widget.hpp"
#include "gui/widgets/grid.hpp"
@ -1306,9 +1307,18 @@ void window::signal_handler_sdl_key_down(const event::ui_event event,
}
}
}
if(!enter_disabled_ && (key == SDLK_KP_ENTER || key == SDLK_RETURN)) {
set_retval(retval::OK);
handled = true;
if(key == SDLK_KP_ENTER || key == SDLK_RETURN) {
if (mod & (KMOD_CTRL | KMOD_ALT | KMOD_GUI | KMOD_SHIFT)) {
// Don't handle if modifier is pressed
handled = false;
} else {
// Trigger window OK button only if Enter enabled,
// otherwise pass handling to widget
if (!enter_disabled_) {
set_retval(retval::OK);
handled = true;
}
}
} else if(key == SDLK_ESCAPE && !escape_disabled_) {
set_retval(retval::CANCEL);
handled = true;