diff --git a/dlangui.sln b/dlangui.sln index c0b832ec..5bba1782 100644 --- a/dlangui.sln +++ b/dlangui.sln @@ -34,6 +34,9 @@ EndProject Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "dsfml", "..\DSFML\dsfml\dsfml.visualdproj", "{DB490C05-D9F8-431C-91DD-CEE646A64FDA}" EndProject Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "dmledit", "examples\dmledit\dmledit.visualdproj", "{06D73450-2919-48A8-B2C3-738B12505D74}" + ProjectSection(ProjectDependencies) = postProject + {5FF17402-9997-4D0E-8068-6D84B8769D98} = {5FF17402-9997-4D0E-8068-6D84B8769D98} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/examples/dmledit/src/dmledit.d b/examples/dmledit/src/dmledit.d index d15abbf9..bb52f93e 100644 --- a/examples/dmledit/src/dmledit.d +++ b/examples/dmledit/src/dmledit.d @@ -4,13 +4,131 @@ import dlangui; mixin APP_ENTRY_POINT; +// action codes +enum IDEActions : int { + //ProjectOpen = 1010000, + FileNew = 1010000, + FileOpen, + FileSave, + FileSaveAs, + FileSaveAll, + FileClose, + FileExit, + EditPreferences, +} + +// actions +const Action ACTION_FILE_NEW = new Action(IDEActions.FileNew, "MENU_FILE_NEW"c, "document-new", KeyCode.KEY_N, KeyFlag.Control); +const Action ACTION_FILE_SAVE = (new Action(IDEActions.FileSave, "MENU_FILE_SAVE"c, "document-save", KeyCode.KEY_S, KeyFlag.Control)).disableByDefault(); +const Action ACTION_FILE_SAVE_AS = (new Action(IDEActions.FileSaveAs, "MENU_FILE_SAVE_AS"c)).disableByDefault(); +const Action ACTION_FILE_OPEN = new Action(IDEActions.FileOpen, "MENU_FILE_OPEN"c, "document-open", KeyCode.KEY_O, KeyFlag.Control); +const Action ACTION_FILE_EXIT = new Action(IDEActions.FileExit, "MENU_FILE_EXIT"c, "document-close"c, KeyCode.KEY_X, KeyFlag.Alt); +const Action ACTION_EDIT_COPY = (new Action(EditorActions.Copy, "MENU_EDIT_COPY"c, "edit-copy"c, KeyCode.KEY_C, KeyFlag.Control)).addAccelerator(KeyCode.INS, KeyFlag.Control).disableByDefault(); +const Action ACTION_EDIT_PASTE = (new Action(EditorActions.Paste, "MENU_EDIT_PASTE"c, "edit-paste"c, KeyCode.KEY_V, KeyFlag.Control)).addAccelerator(KeyCode.INS, KeyFlag.Shift).disableByDefault(); +const Action ACTION_EDIT_CUT = (new Action(EditorActions.Cut, "MENU_EDIT_CUT"c, "edit-cut"c, KeyCode.KEY_X, KeyFlag.Control)).addAccelerator(KeyCode.DEL, KeyFlag.Shift).disableByDefault(); +const Action ACTION_EDIT_UNDO = (new Action(EditorActions.Undo, "MENU_EDIT_UNDO"c, "edit-undo"c, KeyCode.KEY_Z, KeyFlag.Control)).disableByDefault(); +const Action ACTION_EDIT_REDO = (new Action(EditorActions.Redo, "MENU_EDIT_REDO"c, "edit-redo"c, KeyCode.KEY_Y, KeyFlag.Control)).addAccelerator(KeyCode.KEY_Z, KeyFlag.Control|KeyFlag.Shift).disableByDefault(); +const Action ACTION_EDIT_INDENT = (new Action(EditorActions.Indent, "MENU_EDIT_INDENT"c, "edit-indent"c, KeyCode.TAB, 0)).addAccelerator(KeyCode.KEY_BRACKETCLOSE, KeyFlag.Control).disableByDefault(); +const Action ACTION_EDIT_UNINDENT = (new Action(EditorActions.Unindent, "MENU_EDIT_UNINDENT"c, "edit-unindent", KeyCode.TAB, KeyFlag.Shift)).addAccelerator(KeyCode.KEY_BRACKETOPEN, KeyFlag.Control).disableByDefault(); +const Action ACTION_EDIT_TOGGLE_LINE_COMMENT = (new Action(EditorActions.ToggleLineComment, "MENU_EDIT_TOGGLE_LINE_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control)).disableByDefault(); +const Action ACTION_EDIT_TOGGLE_BLOCK_COMMENT = (new Action(EditorActions.ToggleBlockComment, "MENU_EDIT_TOGGLE_BLOCK_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control|KeyFlag.Shift)).disableByDefault(); +const Action ACTION_EDIT_PREFERENCES = (new Action(IDEActions.EditPreferences, "MENU_EDIT_PREFERENCES"c, null)).disableByDefault(); + +class EditFrame : AppFrame { + + MenuItem mainMenuItems; + + override protected void init() { + _appName = "DMLEdit"; + super.init(); + } + /// create main menu + override protected MainMenu createMainMenu() { + return new MainMenu(new MenuItem()); + mainMenuItems = new MenuItem(); + MenuItem fileItem = new MenuItem(new Action(1, "MENU_FILE")); + fileItem.add(ACTION_FILE_NEW, ACTION_FILE_OPEN, + ACTION_FILE_EXIT); + + MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT")); + editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, + ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, + ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT); + + editItem.add(ACTION_EDIT_PREFERENCES); + + MainMenu mainMenu = new MainMenu(mainMenuItems); + return mainMenu; + } + + + /// create app toolbars + override protected ToolBarHost createToolbars() { + ToolBarHost res = new ToolBarHost(); + ToolBar tb; + tb = res.getOrAddToolbar("Standard"); + tb.addButtons(ACTION_FILE_NEW, ACTION_FILE_OPEN, ACTION_FILE_SAVE); + + tb = res.getOrAddToolbar("Edit"); + tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR, + ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT); + return res; + } + + /// create app body widget + override protected Widget createBody() { + VerticalLayout bodyWidget = new VerticalLayout(); + bodyWidget.layoutWidth = FILL_PARENT; + bodyWidget.layoutHeight = FILL_PARENT; + HorizontalLayout hlayout = new HorizontalLayout(); + hlayout.layoutWidth = makePercentSize(50); + hlayout.layoutHeight = FILL_PARENT; + SourceEdit editor = new SourceEdit(); + hlayout.addChild(editor); + ScrollWidget preview = new ScrollWidget(); + preview.layoutWidth = FILL_PARENT; + preview.layoutHeight = FILL_PARENT; + hlayout.addChild(preview); + bodyWidget.addChild(hlayout); + return bodyWidget; + } + +} + /// entry point for dlangui based application extern (C) int UIAppMain(string[] args) { + + // embed non-standard resources listed in views/resources.list into executable + embeddedResourceList.addResources(embedResourcesFromList!("resources.list")()); + // create window - Window window = Platform.instance.createWindow("DlangUI example - HelloWorld", null); + Window window = Platform.instance.createWindow("DlangUI ML editor"d, null, WindowFlag.Resizable, 700, 470); // create some widget to show in window - window.mainWidget = (new Button()).text("Hello, world!"d).margins(Rect(20,20,20,20)); + window.windowIcon = drawableCache.getImage("dlangui-logo1"); + + + // create some widget to show in window + window.mainWidget = new EditFrame(); + /* + parseML(q{ + VerticalLayout { + id: vlayout + margins: Rect { left: 5; right: 3; top: 2; bottom: 4 } + padding: Rect { 5, 4, 3, 2 } // same as Rect { left: 5; top: 4; right: 3; bottom: 2 } + TextWidget { + id: myLabel1 + text: "Some text"; padding: 5 + enabled: false + } + TextWidget { + id: myLabel2 + text: SOME_TEXT_RESOURCE_ID; margins: 5 + enabled: true + } + } + }); + */ // show window window.show(); diff --git a/examples/dmledit/views/res/mdpi/document-new.png b/examples/dmledit/views/res/mdpi/document-new.png new file mode 100644 index 00000000..a956a809 Binary files /dev/null and b/examples/dmledit/views/res/mdpi/document-new.png differ diff --git a/examples/dmledit/views/resources.list b/examples/dmledit/views/resources.list index 5bdf3a30..d7e0641a 100644 --- a/examples/dmledit/views/resources.list +++ b/examples/dmledit/views/resources.list @@ -2,6 +2,7 @@ res/i18n/en.ini res/i18n/ru.ini res/mdpi/cr3_logo.png res/mdpi/document-close.png +res/mdpi/document-new.png res/mdpi/document-open-recent.png res/mdpi/document-open.png res/mdpi/document-properties.png diff --git a/src/dlangui/core/parser.d b/src/dlangui/core/parser.d index 862071f1..3a81982b 100644 --- a/src/dlangui/core/parser.d +++ b/src/dlangui/core/parser.d @@ -1,3 +1,18 @@ +// Written in the D programming language. + +/** +This module is DML (DlangUI Markup Language) parser - similar to QML in QtQuick + +Synopsis: + +---- +// helloworld +---- + +Copyright: Vadim Lopatin, 2015 +License: Boost License 1.0 +Authors: Vadim Lopatin, coolreader.org@gmail.com + */ module dlangui.core.parser; import dlangui.core.linestream; @@ -21,7 +36,7 @@ class ParserException : Exception { @property int pos() { return _pos; } this(string msg, string file, int line, int pos) { - super(msg ~ " at " ~ _file ~ " line " ~ to!string(line) ~ " column " ~ to!string(pos)); + super(msg ~ " at " ~ file ~ " line " ~ to!string(line) ~ " column " ~ to!string(pos)); _msg = msg; _file = file; _line = line; @@ -29,6 +44,28 @@ class ParserException : Exception { } } +/// parser exception - unknown (unregistered) widget name +class UnknownWidgetException : ParserException { + protected string _objectName; + + @property string objectName() { return _objectName; } + + this(string msg, string objectName, string file, int line, int pos) { + super(msg is null ? "Unknown widget name: " ~ objectName : msg, file, line, pos); + _objectName = objectName; + } +} + +/// parser exception - unknown property for widget +class UnknownPropertyException : UnknownWidgetException { + protected string _propName; + + @property string propName() { return _propName; } + + this(string msg, string objectName, string propName, string file, int line, int pos) { + super(msg is null ? "Unknown property " ~ objectName ~ "." ~ propName : msg, objectName, file, line, pos); + } +} enum TokenType : ushort { /// end of file @@ -406,6 +443,14 @@ class Tokenizer { throw new ParserException(msg ~ getContextSource(), _filename, _token.line, _token.pos); } + void emitUnknownPropertyError(string objectName, string propName) { + throw new UnknownPropertyException("Unknown property " ~ objectName ~ "." ~ propName ~ getContextSource(), objectName, propName, _filename, _token.line, _token.pos); + } + + void emitUnknownObjectError(string objectName) { + throw new UnknownWidgetException("Unknown widget type " ~ objectName ~ getContextSource(), objectName, _filename, _token.line, _token.pos); + } + void emitError(string msg, ref const Token token) { throw new ParserException(msg, _filename, token.line, token.pos); } @@ -481,6 +526,14 @@ class MLParser { _tokenizer.emitError(msg); } + protected void unknownObjectError(string objectName) { + _tokenizer.emitUnknownObjectError(objectName); + } + + protected void unknownPropertyError(string objectName, string propName) { + _tokenizer.emitUnknownPropertyError(objectName, propName); + } + Widget createWidget(string name) { auto metadata = findWidgetMetadata(name); if (!metadata) diff --git a/src/dlangui/core/types.d b/src/dlangui/core/types.d index 0108e4f9..bdcc1b08 100644 --- a/src/dlangui/core/types.d +++ b/src/dlangui/core/types.d @@ -215,6 +215,29 @@ int makePercentSize(int percent) { return (percent * 100) | SIZE_IN_PERCENTS_FLAG; } +/// make size value with SIZE_IN_PERCENTS_FLAG set +int makePercentSize(double percent) { + return cast(int)(percent * 100) | SIZE_IN_PERCENTS_FLAG; +} + +/// returns true for WRAP_CONTENT, WRAP_CONTENT, SIZE_UNSPECIFIED +bool isSpecialSize(int sz) { + // don't forget to update if more special constants added + return (sz & (WRAP_CONTENT | FILL_PARENT | SIZE_UNSPECIFIED)) != 0; +} + +/// returns true if size has SIZE_IN_PERCENTS_FLAG bit set +bool isPercentSize(int size) { + return (size & SIZE_IN_PERCENTS_FLAG) != 0; +} + +/// if size has SIZE_IN_PERCENTS_FLAG bit set, returns percent of baseSize, otherwise returns size unchanged +int fromPercentSize(int size, int baseSize) { + if (isPercentSize(size)) + return cast(int)(cast(long)(size & ~SIZE_IN_PERCENTS_FLAG) * baseSize / 10000); + return size; +} + /// screen dots per inch private __gshared int PRIVATE_SCREEN_DPI = 96; diff --git a/src/dlangui/package.d b/src/dlangui/package.d index 2a477cc2..46995fc6 100644 --- a/src/dlangui/package.d +++ b/src/dlangui/package.d @@ -71,6 +71,7 @@ public import dlangui.widgets.popup; public import dlangui.widgets.appframe; public import dlangui.widgets.statusline; public import dlangui.widgets.docks; +public import dlangui.widgets.toolbars; public import dlangui.platforms.common.platform; public import dlangui.core.parser; diff --git a/src/dlangui/widgets/appframe.d b/src/dlangui/widgets/appframe.d index 5b688069..3d2b9e62 100644 --- a/src/dlangui/widgets/appframe.d +++ b/src/dlangui/widgets/appframe.d @@ -80,6 +80,13 @@ class AppFrame : VerticalLayout, MenuItemClickHandler, MenuItemActionHandler { protected ulong _currentBackgroundOperationTimer; + this() { + super("APP_FRAME"); + layoutWidth = FILL_PARENT; + layoutHeight = FILL_PARENT; + _appName = "dlangui"; + init(); + } protected string _appName; /// override to return some identifier for app, e.g. to use as settings directory name @@ -210,14 +217,6 @@ class AppFrame : VerticalLayout, MenuItemClickHandler, MenuItemActionHandler { /// body widget @property Widget frameBody() { return _body; } - this() { - super("APP_FRAME"); - layoutWidth = FILL_PARENT; - layoutHeight = FILL_PARENT; - _appName = "dlangui"; - init(); - } - /// map key to action override Action findKeyAction(uint keyCode, uint flags) { if (_mainMenu) { diff --git a/src/dlangui/widgets/styles.d b/src/dlangui/widgets/styles.d index dbd38a7e..74495bc4 100644 --- a/src/dlangui/widgets/styles.d +++ b/src/dlangui/widgets/styles.d @@ -205,12 +205,6 @@ immutable uint TEXT_FLAGS_USE_PARENT = uint.max - 1; /// to take layout weight from parent immutable int WEIGHT_UNSPECIFIED = -1; -/// returns true for WRAP_CONTENT, WRAP_CONTENT, SIZE_UNSPECIFIED -bool isSpecialSize(int sz) { - // don't forget to update if more special constants added - return sz >= WRAP_CONTENT; -} - /// Align option bit constants enum Align : ubyte { /// alignment is not specified diff --git a/src/dlangui/widgets/widget.d b/src/dlangui/widgets/widget.d index c922cfff..f2dab4cd 100644 --- a/src/dlangui/widgets/widget.d +++ b/src/dlangui/widgets/widget.d @@ -1223,9 +1223,13 @@ class Widget { // check for fixed size set in layoutWidth, layoutHeight int lh = layoutHeight; int lw = layoutWidth; - if (!isSpecialSize(lh)) + if (isPercentSize(lh) && parentHeight != SIZE_UNSPECIFIED) + dy = fromPercentSize(lh, parentHeight); + else if (!isSpecialSize(lh)) dy = lh; - if (!isSpecialSize(lw)) + if (isPercentSize(lw) && parentWidth != SIZE_UNSPECIFIED) + dy = fromPercentSize(lw, parentWidth); + else if (!isSpecialSize(lw)) dx = lw; // apply min/max width and height constraints int minw = minWidth;