support underlined text style, and underlining of hotkey chars in text

This commit is contained in:
Vadim Lopatin 2014-05-08 08:59:33 +04:00
parent e0c9b8bc9c
commit 2719aecadb
5 changed files with 95 additions and 28 deletions

View File

@ -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));

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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) ;

View File

@ -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) {