mirror of https://github.com/buggins/dlangui.git
support underlined text style, and underlining of hotkey chars in text
This commit is contained in:
parent
e0c9b8bc9c
commit
2719aecadb
|
@ -57,25 +57,25 @@ extern (C) int UIAppMain(string[] args) {
|
|||
static if (true) {
|
||||
VerticalLayout contentLayout = new VerticalLayout();
|
||||
MenuItem mainMenuItems = new MenuItem();
|
||||
MenuItem fileItem = new MenuItem(new Action(1, "File"d));
|
||||
fileItem.add(new Action(10, "Open..."d, "document-open", KeyCode.KEY_O, KeyFlag.Control));
|
||||
fileItem.add(new Action(11, "Save..."d, "document-save", KeyCode.KEY_S, KeyFlag.Control));
|
||||
MenuItem fileItem = new MenuItem(new Action(1, "&File"d));
|
||||
fileItem.add(new Action(10, "&Open..."d, "document-open", KeyCode.KEY_O, KeyFlag.Control));
|
||||
fileItem.add(new Action(11, "&Save..."d, "document-save", KeyCode.KEY_S, KeyFlag.Control));
|
||||
MenuItem openRecentItem = new MenuItem(new Action(13, "Open recent..."d, "document-open-recent"));
|
||||
openRecentItem.add(new Action(100, "File 1"d));
|
||||
openRecentItem.add(new Action(101, "File 2"d));
|
||||
openRecentItem.add(new Action(102, "File 3"d));
|
||||
openRecentItem.add(new Action(103, "File 4"d));
|
||||
openRecentItem.add(new Action(104, "File 5"d));
|
||||
openRecentItem.add(new Action(100, "&1: File 1"d));
|
||||
openRecentItem.add(new Action(101, "&2: File 2"d));
|
||||
openRecentItem.add(new Action(102, "&3: File 3"d));
|
||||
openRecentItem.add(new Action(103, "&4: File 4"d));
|
||||
openRecentItem.add(new Action(104, "&5: File 5"d));
|
||||
fileItem.add(openRecentItem);
|
||||
fileItem.add(new Action(12, "Exit"d, "document-close", KeyCode.KEY_X, KeyFlag.Alt));
|
||||
MenuItem editItem = new MenuItem(new Action(2, "Edit"d));
|
||||
fileItem.add(new Action(12, "E&xit"d, "document-close", KeyCode.KEY_X, KeyFlag.Alt));
|
||||
MenuItem editItem = new MenuItem(new Action(2, "&Edit"d));
|
||||
editItem.add(new Action(EditorActions.Copy, "Copy"d, "edit-copy", KeyCode.KEY_C, KeyFlag.Control));
|
||||
editItem.add(new Action(EditorActions.Paste, "Paste"d, "edit-paste", KeyCode.KEY_V, KeyFlag.Control));
|
||||
editItem.add(new Action(EditorActions.Cut, "Cut"d, "edit-cut", KeyCode.KEY_X, KeyFlag.Control));
|
||||
editItem.add(new Action(EditorActions.Undo, "Undo"d, "edit-undo", KeyCode.KEY_Z, KeyFlag.Control));
|
||||
editItem.add(new Action(EditorActions.Redo, "Redo"d, "edit-redo", KeyCode.KEY_Y, KeyFlag.Control));
|
||||
editItem.add(new Action(20, "Preferences..."d));
|
||||
MenuItem windowItem = new MenuItem(new Action(3, "Window"d));
|
||||
MenuItem windowItem = new MenuItem(new Action(3, "&Window"d));
|
||||
windowItem.add(new Action(30, "Preferences"d));
|
||||
MenuItem helpItem = new MenuItem(new Action(4, "Help"d));
|
||||
helpItem.add(new Action(40, "View Help"d));
|
||||
|
|
|
@ -22,6 +22,7 @@ module dlangui.graphics.fonts;
|
|||
public import dlangui.graphics.drawbuf;
|
||||
public import dlangui.core.types;
|
||||
public import dlangui.core.logger;
|
||||
private import dlangui.widgets.styles;
|
||||
import std.algorithm;
|
||||
|
||||
/// font family
|
||||
|
@ -46,11 +47,11 @@ enum FontWeight : int {
|
|||
Bold = 800
|
||||
}
|
||||
|
||||
immutable dchar UNICODE_SOFT_HYPHEN_CODE = 0x00ad;
|
||||
immutable dchar UNICODE_ZERO_WIDTH_SPACE = 0x200b;
|
||||
immutable dchar UNICODE_NO_BREAK_SPACE = 0x00a0;
|
||||
immutable dchar UNICODE_HYPHEN = 0x2010;
|
||||
immutable dchar UNICODE_NB_HYPHEN = 0x2011;
|
||||
immutable dchar UNICODE_SOFT_HYPHEN_CODE = 0x00ad;
|
||||
immutable dchar UNICODE_ZERO_WIDTH_SPACE = 0x200b;
|
||||
immutable dchar UNICODE_NO_BREAK_SPACE = 0x00a0;
|
||||
immutable dchar UNICODE_HYPHEN = 0x2010;
|
||||
immutable dchar UNICODE_NB_HYPHEN = 0x2011;
|
||||
|
||||
|
||||
version (USE_OPENGL) {
|
||||
|
@ -162,6 +163,7 @@ struct GlyphCache
|
|||
}
|
||||
}
|
||||
|
||||
immutable int MAX_WIDTH_UNSPECIFIED = int.max;
|
||||
|
||||
/// Font object
|
||||
class Font : RefCountedObject {
|
||||
|
@ -218,7 +220,7 @@ class Font : RefCountedObject {
|
|||
* tabSize = tabulation size, in number of spaces
|
||||
* tabOffset = when string is drawn not from left position, use to move tab stops left/right
|
||||
******************************************************************************************/
|
||||
int measureText(const dchar[] text, ref int[] widths, int maxWidth=int.max, int tabSize = 4, int tabOffset = 0) {
|
||||
int measureText(const dchar[] text, ref int[] widths, int maxWidth=MAX_WIDTH_UNSPECIFIED, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
|
||||
if (text.length == 0)
|
||||
return 0;
|
||||
const dchar * pstr = text.ptr;
|
||||
|
@ -242,7 +244,10 @@ class Font : RefCountedObject {
|
|||
charsMeasured = i + 1;
|
||||
x = tabPosition;
|
||||
continue;
|
||||
}
|
||||
} else if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys))) {
|
||||
pwidths[i] = x;
|
||||
continue; // skip '&' in hot key when measuring
|
||||
}
|
||||
Glyph * glyph = getCharGlyph(pstr[i], true); // TODO: what is better
|
||||
//auto measureEnd = std.datetime.Clock.currAppTick;
|
||||
//auto duration = measureEnd - measureStart;
|
||||
|
@ -275,10 +280,10 @@ class Font : RefCountedObject {
|
|||
* text = text string to measure
|
||||
* maxWidth = maximum width - measure is stopping if max width is reached
|
||||
************************************************************************/
|
||||
Point textSize(const dchar[] text, int maxWidth = int.max) {
|
||||
Point textSize(const dchar[] text, int maxWidth = MAX_WIDTH_UNSPECIFIED, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
|
||||
if (_textSizeBuffer.length < text.length + 1)
|
||||
_textSizeBuffer.length = text.length + 1;
|
||||
int charsMeasured = measureText(text, _textSizeBuffer, maxWidth);
|
||||
int charsMeasured = measureText(text, _textSizeBuffer, maxWidth, tabSize, tabOffset, textFlags);
|
||||
if (charsMeasured < 1)
|
||||
return Point(0,0);
|
||||
return Point(_textSizeBuffer[charsMeasured - 1], height);
|
||||
|
@ -295,26 +300,46 @@ class Font : RefCountedObject {
|
|||
* color = color for drawing of glyphs
|
||||
* tabSize = tabulation size, in number of spaces
|
||||
* tabOffset = when string is drawn not from left position, use to move tab stops left/right
|
||||
* textFlags = set of TextFlag bit fields
|
||||
****************************************************************************************/
|
||||
void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color, int tabSize = 4, int tabOffset = 0) {
|
||||
void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
|
||||
if (text.length == 0)
|
||||
return; // nothing to draw - empty text
|
||||
if (_textSizeBuffer.length < text.length)
|
||||
_textSizeBuffer.length = text.length;
|
||||
int charsMeasured = measureText(text, _textSizeBuffer, int.max, tabSize, tabOffset);
|
||||
int charsMeasured = measureText(text, _textSizeBuffer, MAX_WIDTH_UNSPECIFIED, tabSize, tabOffset, textFlags);
|
||||
Rect clip = buf.clipOrFullRect;
|
||||
if (clip.empty)
|
||||
return; // not visible - clipped out
|
||||
if (y + height < clip.top || y >= clip.bottom)
|
||||
return; // not visible - fully above or below clipping rectangle
|
||||
int _baseline = baseline;
|
||||
bool underline = (textFlags & TextFlag.Underline) != 0;
|
||||
int underlineHeight = 1;
|
||||
int underlineY = y + _baseline + underlineHeight * 2;
|
||||
for (int i = 0; i < charsMeasured; i++) {
|
||||
dchar ch = text[i];
|
||||
if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys))) {
|
||||
if (textFlags & TextFlag.UnderlineHotKeys)
|
||||
underline = true; // turn ON underline for hot key
|
||||
continue; // skip '&' in hot key when measuring
|
||||
}
|
||||
int xx = (i > 0) ? _textSizeBuffer[i - 1] : 0;
|
||||
if (x + xx > clip.right)
|
||||
break;
|
||||
if (x + xx + 255 < clip.left)
|
||||
continue; // far at left of clipping region
|
||||
dchar ch = text[i];
|
||||
|
||||
if (underline) {
|
||||
int xx2 = _textSizeBuffer[i];
|
||||
// draw underline
|
||||
if (xx2 > xx)
|
||||
buf.fillRect(Rect(x + xx, underlineY, x + xx2, underlineY + underlineHeight), color);
|
||||
// turn off underline after hot key
|
||||
if (!(textFlags & TextFlag.Underline))
|
||||
underline = false;
|
||||
}
|
||||
|
||||
if (ch == ' ' || ch == '\t')
|
||||
continue;
|
||||
Glyph * glyph = getCharGlyph(ch);
|
||||
|
|
|
@ -89,7 +89,7 @@ class TextWidget : Widget {
|
|||
override void measure(int parentWidth, int parentHeight) {
|
||||
FontRef font = font();
|
||||
//auto measureStart = std.datetime.Clock.currAppTick;
|
||||
Point sz = font.textSize(text);
|
||||
Point sz = font.textSize(text, MAX_WIDTH_UNSPECIFIED, 4, 0, textFlags);
|
||||
//auto measureEnd = std.datetime.Clock.currAppTick;
|
||||
//auto duration = measureEnd - measureStart;
|
||||
//if (duration.length > 10)
|
||||
|
@ -108,7 +108,7 @@ class TextWidget : Widget {
|
|||
FontRef font = font();
|
||||
Point sz = font.textSize(text);
|
||||
applyAlign(rc, sz);
|
||||
font.drawText(buf, rc.left, rc.top, text, textColor);
|
||||
font.drawText(buf, rc.left, rc.top, text, textColor, 4, 0, textFlags);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ immutable ubyte FONT_STYLE_NORMAL = 0x00;
|
|||
immutable ubyte FONT_STYLE_ITALIC = 0x01;
|
||||
/// use as widget.layout() param to avoid applying of parent size
|
||||
immutable int SIZE_UNSPECIFIED = int.max;
|
||||
immutable uint TEXT_FLAGS_UNSPECIFIED = uint.max;
|
||||
|
||||
immutable int FILL_PARENT = int.max - 1;
|
||||
immutable int WRAP_CONTENT = int.max - 2;
|
||||
|
@ -53,6 +54,16 @@ enum Align : ubyte {
|
|||
TopLeft = Left | Top,
|
||||
}
|
||||
|
||||
/// text drawing flag bits
|
||||
enum TextFlag : uint {
|
||||
/// text contains hot key prefixed with & char (e.g. "&File")
|
||||
HotKeys = 1,
|
||||
/// underline hot key when drawing
|
||||
UnderlineHotKeys = 2,
|
||||
/// underline text when drawing
|
||||
Underline = 4
|
||||
}
|
||||
|
||||
class DrawableAttribute {
|
||||
protected string _id;
|
||||
protected string _drawableId;
|
||||
|
@ -93,6 +104,7 @@ class Style {
|
|||
protected ushort _fontWeight = FONT_WEIGHT_UNSPECIFIED;
|
||||
protected uint _backgroundColor = COLOR_UNSPECIFIED;
|
||||
protected uint _textColor = COLOR_UNSPECIFIED;
|
||||
protected uint _textFlags = 0;
|
||||
protected string _fontFace;
|
||||
protected string _backgroundImageId;
|
||||
protected Rect _padding;
|
||||
|
@ -260,7 +272,15 @@ class Style {
|
|||
return parentStyle.textColor;
|
||||
}
|
||||
|
||||
//===================================================
|
||||
/// text flags
|
||||
@property uint textFlags() const {
|
||||
if (_textFlags != TEXT_FLAGS_UNSPECIFIED)
|
||||
return _textFlags;
|
||||
else
|
||||
return parentStyle.textFlags;
|
||||
}
|
||||
|
||||
//===================================================
|
||||
// background
|
||||
|
||||
/// background color
|
||||
|
@ -428,6 +448,11 @@ class Style {
|
|||
return this;
|
||||
}
|
||||
|
||||
@property Style textFlags(uint flags) {
|
||||
_textFlags = flags;
|
||||
return this;
|
||||
}
|
||||
|
||||
@property Style backgroundColor(uint color) {
|
||||
_backgroundColor = color;
|
||||
_backgroundImageId = null;
|
||||
|
@ -514,6 +539,8 @@ class Style {
|
|||
child._stateMask = stateMask;
|
||||
child._stateValue = stateValue;
|
||||
child._backgroundColor = COLOR_UNSPECIFIED;
|
||||
child._textColor = COLOR_UNSPECIFIED;
|
||||
child._textFlags = TEXT_FLAGS_UNSPECIFIED;
|
||||
_substates ~= child;
|
||||
return child;
|
||||
}
|
||||
|
@ -573,6 +600,8 @@ class Theme : Style {
|
|||
style._align = Align.Unspecified; // inherit
|
||||
style._padding.left = SIZE_UNSPECIFIED; // inherit
|
||||
style._margins.left = SIZE_UNSPECIFIED; // inherit
|
||||
style._textColor = COLOR_UNSPECIFIED; // inherit
|
||||
style._textFlags = TEXT_FLAGS_UNSPECIFIED; // inherit
|
||||
return style;
|
||||
}
|
||||
|
||||
|
@ -725,7 +754,7 @@ Theme createDefaultTheme() {
|
|||
menuItem.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
|
||||
menuItem.createState(State.Hovered, State.Hovered).backgroundColor(0xC0FFFF00);
|
||||
res.createSubstyle("MENU_ICON").setMargins(2,2,2,2).alignment(Align.VCenter|Align.Left);
|
||||
res.createSubstyle("MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left);
|
||||
res.createSubstyle("MENU_LABEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TextFlag.UnderlineHotKeys);
|
||||
res.createSubstyle("MENU_ACCEL").setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left);
|
||||
|
||||
Style transparentButtonBackground = res.createSubstyle("TRANSPARENT_BUTTON_BACKGROUND").backgroundImageId("transparent_button_background").setPadding(4,2,4,2); //.backgroundColor(0xE0E080) ;
|
||||
|
|
|
@ -299,7 +299,20 @@ class Widget {
|
|||
invalidate();
|
||||
return this;
|
||||
}
|
||||
/// returns font face
|
||||
/// get text flags (bit set of TextFlag enum values)
|
||||
@property uint textFlags() const { return stateStyle.textFlags; }
|
||||
/// set text flags (bit set of TextFlag enum values)
|
||||
@property Widget textFlags(uint value) {
|
||||
ownStyle.textFlags = value;
|
||||
bool oldHotkeys = (ownStyle.textFlags & (TextFlag.HotKeys | TextFlag.UnderlineHotKeys)) != 0;
|
||||
bool newHotkeys = (value & (TextFlag.HotKeys | TextFlag.UnderlineHotKeys)) != 0;
|
||||
if (oldHotkeys != newHotkeys)
|
||||
requestLayout();
|
||||
else
|
||||
invalidate();
|
||||
return this;
|
||||
}
|
||||
/// returns font face
|
||||
@property string fontFace() const { return stateStyle.fontFace; }
|
||||
/// set font face for widget - override one from style
|
||||
@property Widget fontFace(string face) {
|
||||
|
|
Loading…
Reference in New Issue