├── resources
├── dido.png
├── dub.png
├── run.png
├── test.png
├── build.png
├── corner.png
├── cycle.png
└── dlang.png
├── screenshots
└── dido.jpg
├── fonts
├── SourceCodePro-Bold.ttf
├── SourceCodePro-Black.ttf
├── SourceCodePro-Light.ttf
├── SourceCodePro-Medium.ttf
├── SourceCodePro-Regular.ttf
├── SourceCodePro-Semibold.ttf
├── SourceCodePro-ExtraLight.ttf
└── LICENSE.txt
├── source
├── dido
│ ├── config.d
│ ├── gui
│ │ ├── package.d
│ │ ├── context.d
│ │ ├── button.d
│ │ ├── combobox.d
│ │ ├── scrollbar.d
│ │ ├── font.d
│ │ └── element.d
│ ├── panel
│ │ ├── package.d
│ │ ├── mainpanel.d
│ │ ├── images.d
│ │ ├── solutionpanel.d
│ │ ├── westpanel.d
│ │ ├── outputpanel.d
│ │ ├── commandlinepanel.d
│ │ ├── menupanel.d
│ │ └── textarea.d
│ ├── buffer
│ │ ├── analysis.d
│ │ ├── buffercommand.d
│ │ ├── bufferiterator.d
│ │ ├── selection.d
│ │ └── buffer.d
│ ├── colors.d
│ ├── window.d
│ ├── project.d
│ ├── builder.d
│ ├── app.d
│ ├── engine.d
│ └── pngload.d
└── main.d
├── dub.json
├── README.md
└── UNLICENSE.txt
/resources/dido.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/resources/dido.png
--------------------------------------------------------------------------------
/resources/dub.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/resources/dub.png
--------------------------------------------------------------------------------
/resources/run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/resources/run.png
--------------------------------------------------------------------------------
/resources/test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/resources/test.png
--------------------------------------------------------------------------------
/resources/build.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/resources/build.png
--------------------------------------------------------------------------------
/resources/corner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/resources/corner.png
--------------------------------------------------------------------------------
/resources/cycle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/resources/cycle.png
--------------------------------------------------------------------------------
/resources/dlang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/resources/dlang.png
--------------------------------------------------------------------------------
/screenshots/dido.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/screenshots/dido.jpg
--------------------------------------------------------------------------------
/fonts/SourceCodePro-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/fonts/SourceCodePro-Bold.ttf
--------------------------------------------------------------------------------
/fonts/SourceCodePro-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/fonts/SourceCodePro-Black.ttf
--------------------------------------------------------------------------------
/fonts/SourceCodePro-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/fonts/SourceCodePro-Light.ttf
--------------------------------------------------------------------------------
/fonts/SourceCodePro-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/fonts/SourceCodePro-Medium.ttf
--------------------------------------------------------------------------------
/fonts/SourceCodePro-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/fonts/SourceCodePro-Regular.ttf
--------------------------------------------------------------------------------
/fonts/SourceCodePro-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/fonts/SourceCodePro-Semibold.ttf
--------------------------------------------------------------------------------
/fonts/SourceCodePro-ExtraLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0nce/dido/HEAD/fonts/SourceCodePro-ExtraLight.ttf
--------------------------------------------------------------------------------
/source/dido/config.d:
--------------------------------------------------------------------------------
1 | module dido.config;
2 |
3 |
4 | class DidoConfig
5 | {
6 | public:
7 |
8 | string fontFace = "fonts/SourceCodePro-Semibold.ttf";
9 | int fontSize = 20;
10 |
11 | this()
12 | {
13 | }
14 | }
--------------------------------------------------------------------------------
/source/dido/gui/package.d:
--------------------------------------------------------------------------------
1 | module dido.gui;
2 |
3 | public import dido.gui.element;
4 | public import dido.gui.context;
5 | public import dido.gui.font;
6 | public import dido.gui.scrollbar;
7 | public import dido.gui.combobox;
8 | public import dido.gui.button;
9 |
--------------------------------------------------------------------------------
/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dido",
3 |
4 | "targetType": "executable",
5 | "importPaths": ["source"],
6 | "sourcePaths": ["source"],
7 | "stringImportPaths": ["resources"],
8 |
9 | "dependencies": {
10 | "gfm:sdl2": "~>6.0",
11 | "gfm:math": "~>6.0",
12 | "scheme-d": "~>1.0"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/source/dido/panel/package.d:
--------------------------------------------------------------------------------
1 | module dido.panel;
2 |
3 | // This is a custom-made GUI for dido
4 | // reflow and render is pretty manual
5 |
6 | public import dido.panel.mainpanel;
7 | public import dido.panel.menupanel;
8 | public import dido.panel.commandlinepanel;
9 | public import dido.panel.textarea;
10 | public import dido.panel.solutionpanel;
11 | public import dido.panel.outputpanel;
12 | public import dido.panel.westpanel;
13 |
--------------------------------------------------------------------------------
/source/dido/buffer/analysis.d:
--------------------------------------------------------------------------------
1 | module dido.buffer.analysis;
2 |
3 | //import std.d.lexer;
4 |
5 | /+
6 | class Analysis
7 | {
8 | this(string filename, string source)
9 | {
10 | _filename = filename;
11 | _stringCache = StringCache(StringCache.defaultBucketCount);
12 | }
13 |
14 | void parse(string filemane, string source)
15 | {
16 | _tokens = getTokensForParser( cast(ubyte[]) source, LexerConfig(filemane, StringBehavior.source), &_stringCache);
17 | }
18 |
19 | private:
20 | string _filename;
21 | StringCache _stringCache;
22 | const(Token)[] _tokens;
23 | }+/
--------------------------------------------------------------------------------
/source/dido/panel/mainpanel.d:
--------------------------------------------------------------------------------
1 | module dido.panel.mainpanel;
2 |
3 | import dido.gui;
4 |
5 | class MainPanel : UIElement
6 | {
7 | public:
8 | this(UIContext context)
9 | {
10 | super(context);
11 | }
12 |
13 | override void reflow(box2i availableSpace)
14 | {
15 | _position = availableSpace;
16 |
17 | // reflow horizontal bars first
18 | child(4).reflow(availableSpace);
19 |
20 |
21 | child(3).reflow(availableSpace);
22 | availableSpace.min.y = child(3).position.max.y;
23 |
24 | child(2).reflow(availableSpace);
25 | availableSpace.max.y = child(2).position.min.y;
26 |
27 | int widthOfWestPanel = (250 + availableSpace.width / 3) / 2;
28 |
29 | box2i spaceForWest = availableSpace;
30 | spaceForWest.max.x = widthOfWestPanel;
31 |
32 | child(1).reflow(spaceForWest);
33 |
34 |
35 | availableSpace.min.x = widthOfWestPanel;
36 | child(0).reflow(availableSpace);
37 | }
38 | }
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/source/dido/panel/images.d:
--------------------------------------------------------------------------------
1 | module dido.panel.images;
2 |
3 | import dido.gui.context;
4 |
5 | static immutable imageDido = cast(immutable(ubyte[])) import("dido.png");
6 | static immutable imageCorner = cast(immutable(ubyte[])) import("corner.png");
7 | static immutable imageBuild = cast(immutable(ubyte[])) import("build.png");
8 | static immutable imageRun = cast(immutable(ubyte[])) import("run.png");
9 | static immutable imageTest = cast(immutable(ubyte[])) import("test.png");
10 | static immutable imageCycle = cast(immutable(ubyte[])) import("cycle.png");
11 | static immutable imageDlang = cast(immutable(ubyte[])) import("dlang.png");
12 | static immutable imageDub = cast(immutable(ubyte[])) import("dub.png");
13 | void addAllImage(UIContext context)
14 | {
15 | context.addImage("corner", imageCorner);
16 | context.addImage("dido", imageDido);
17 | context.addImage("build", imageBuild);
18 | context.addImage("test", imageTest);
19 | context.addImage("run", imageRun);
20 | context.addImage("cycle", imageCycle);
21 | context.addImage("dlang", imageDlang);
22 | context.addImage("dub", imageDub);
23 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Dido is a specific text editor for the D language.
2 |
3 | 
4 |
5 | Status: Design failure. Multiple problems were met. Better start anew.
6 |
7 | ## What's inside
8 |
9 | - Multiple cursors editing
10 | - dido contains a small GUI package built on top of SDL2 renderers
11 | - portable graphics, SDL 2.x is the only dependency: https://www.libsdl.org/
12 | - minimal DUB integration
13 |
14 | ## Current limitations
15 |
16 | - currently not configurable
17 | - no file browser
18 | - Multiple views not supported
19 | - only UTF-8 files support (load and save)
20 | - command language is very incomplete
21 | - currently no syntax highlighting
22 | - see Issues for more limitations
23 |
24 | ## Inspiration
25 |
26 | - dido's internals were heavily inspired by kakoune, a much more advanced and general editor.
27 | https://github.com/mawww/kakoune
28 |
29 | - Sublime Text is an inspiration for various things like multiple cursors
30 |
31 | - Visual Studio is an inspiration for UI
32 |
33 | ## Compiling
34 |
35 | - Build with DUB (a 2.066+ front-end is required)
36 | - Use recent SDL 2.x binaries
37 |
38 |
--------------------------------------------------------------------------------
/UNLICENSE.txt:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
--------------------------------------------------------------------------------
/source/dido/colors.d:
--------------------------------------------------------------------------------
1 | module dido.colors;
2 |
3 | public import std.random;
4 |
5 | import std.algorithm,
6 | std.math;
7 |
8 | public import gfm.math.vector,
9 | gfm.math.funcs,
10 | gfm.math.simplerng;
11 |
12 |
13 | vec3ub rgb(int r, int g, int b) pure nothrow
14 | {
15 | return vec3ub(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b);
16 | }
17 |
18 | vec4ub rgba(int r, int g, int b, int a) pure nothrow
19 | {
20 | return vec4ub(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b, cast(ubyte)a);
21 | }
22 |
23 | vec3ub lerpColor(vec3ub a, vec3ub b, float t) pure nothrow
24 | {
25 | vec3f af = cast(vec3f)a;
26 | vec3f bf = cast(vec3f)b;
27 | vec3f of = af * (1 - t) + bf * t;
28 | return cast(vec3ub)(0.5f + of);
29 | }
30 |
31 | vec4ub lerpColor(vec4ub a, vec4ub b, float t) pure nothrow
32 | {
33 | vec4f af = cast(vec4f)a;
34 | vec4f bf = cast(vec4f)b;
35 | vec4f of = af * (1 - t) + bf * t;
36 | return cast(vec4ub)(0.5f + of);
37 | }
38 |
39 | vec3ub mulColor(vec3ub color, float amount) pure nothrow
40 | {
41 | vec3f fcolor = cast(vec3f)color / 255.0f;
42 | fcolor *= amount;
43 | return cast(vec3ub)(0.5f + fcolor * 255.0f);
44 | }
45 |
46 | vec4ub mulColor(vec4ub color, float amount) pure nothrow
47 | {
48 | return vec4ub(mulColor(color.xyz, amount), color.w);
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/source/dido/window.d:
--------------------------------------------------------------------------------
1 | module dido.window;
2 |
3 | import gfm.sdl2;
4 | import dido.gui.font;
5 |
6 | final class Window
7 | {
8 | public:
9 | this(SDL2 sdl2)
10 | {
11 |
12 | int initialWidth = 800;
13 | int initialHeight = 700;
14 |
15 | _window = new SDL2Window(sdl2, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, initialWidth, initialHeight,
16 | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
17 |
18 | _window.setTitle("Dido v0.0.1");
19 |
20 | // Try creating default renderer
21 |
22 | _renderer = new SDL2Renderer(_window, SDL_RENDERER_SOFTWARE);
23 | _renderer.setBlend(SDL_BLENDMODE_BLEND);
24 |
25 | _initialized = true;
26 | }
27 |
28 | ~this()
29 | {
30 | if (_initialized)
31 | {
32 | _renderer.destroy();
33 | _window.destroy();
34 | _initialized = false;
35 | }
36 | }
37 |
38 | void toggleFullscreen()
39 | {
40 | isFullscreen = !isFullscreen;
41 | if (isFullscreen)
42 | _window.setFullscreenSetting(SDL_WINDOW_FULLSCREEN_DESKTOP);
43 | else
44 | _window.setFullscreenSetting(0);
45 | }
46 |
47 | SDL2Renderer renderer()
48 | {
49 | return _renderer;
50 | }
51 |
52 | int getWidth()
53 | {
54 | return _window.getWidth();
55 | }
56 |
57 | int getHeight()
58 | {
59 | return _window.getHeight();
60 | }
61 |
62 | private:
63 | SDL2Window _window;
64 | SDL2Renderer _renderer;
65 | bool isFullscreen = false;
66 | bool _initialized;
67 | }
--------------------------------------------------------------------------------
/source/dido/buffer/buffercommand.d:
--------------------------------------------------------------------------------
1 | module dido.buffer.buffercommand;
2 |
3 | import dido.buffer.selection;
4 |
5 | enum BufferCommandType
6 | {
7 | CHANGE_CHARS, // edit one text area
8 | BARRIER, // anchors for undo/redo, saves all selections location
9 | SAVE_SELECTIONS
10 |
11 | }
12 |
13 | struct ChangeCharsCommand
14 | {
15 | Selection oldSel;
16 | Selection newSel;
17 | dstring oldContent; // content of selection before applying this BufferCommand
18 | dstring newContent; // content of selection after applying this BufferCommand
19 | }
20 |
21 | struct BarrierCommand
22 | {
23 | }
24 |
25 | struct SaveSelectionsCommand
26 | {
27 | Selection[] selections;
28 | }
29 |
30 | struct BufferCommand
31 | {
32 | BufferCommandType type;
33 | union
34 | {
35 | ChangeCharsCommand changeChars;
36 | BarrierCommand barrier;
37 | SaveSelectionsCommand saveSelections;
38 | }
39 | }
40 |
41 | BufferCommand barrierCommand()
42 | {
43 | BufferCommand command;
44 | command.type = BufferCommandType.BARRIER;
45 | return command;
46 | }
47 |
48 | BufferCommand saveSelectionsCommand(Selection[] selectionToSave)
49 | {
50 | BufferCommand command;
51 | command.type = BufferCommandType.SAVE_SELECTIONS;
52 | command.saveSelections.selections = selectionToSave.dup;
53 | return command;
54 | }
55 |
56 | BufferCommand changeCharsCommand(Selection oldSel,
57 | Selection newSel,
58 | dstring oldContent,
59 | dstring newContent)
60 | {
61 | BufferCommand command;
62 | command.type = BufferCommandType.CHANGE_CHARS;
63 | command.changeChars = ChangeCharsCommand(oldSel, newSel, oldContent.dup, newContent.dup);
64 | return command;
65 | }
66 |
--------------------------------------------------------------------------------
/source/dido/panel/solutionpanel.d:
--------------------------------------------------------------------------------
1 | module dido.panel.solutionpanel;
2 |
3 | import std.path : baseName;
4 |
5 | import dido.project;
6 | import dido.buffer.buffer;
7 | import dido.gui;
8 |
9 | class SolutionPanel : UIElement
10 | {
11 | public:
12 |
13 | this(UIContext context, Project[] projects)
14 | {
15 | super(context);
16 | _cameraY = 0;
17 | _projects = projects;
18 | }
19 |
20 | override void reflow(box2i availableSpace)
21 | {
22 | _position = availableSpace;
23 | }
24 |
25 | override void preRender(SDL2Renderer renderer)
26 | {
27 |
28 | int itemSpace = font.charHeight() + 12;
29 | int marginX = 16;
30 | int marginY = 16;
31 |
32 | foreach(int iproject, project; _projects)
33 | {
34 | int px = marginX;
35 | int py = marginY;
36 | //int width =
37 | //int height = iproject;
38 |
39 |
40 | }
41 |
42 | /* for(int i = 0; i < cast(int)_buffers.length; ++i)
43 | {
44 | renderer.setColor(25, 25, 25, 255);
45 | int rectMargin = 4;
46 | renderer.fillRect(marginX - rectMargin, marginY - rectMargin + i * itemSpace, _position.width - 2 * (marginX - rectMargin), itemSpace - 4);
47 | }
48 |
49 | for(int i = 0; i < cast(int)_buffers.length; ++i)
50 | {
51 | if (i == _bufferSelect)
52 | font.setColor(255, 255, 255, 255);
53 | else
54 | font.setColor(200, 200, 200, 255);
55 | font.renderString(_prettyName[i], marginX, marginY + i * itemSpace);
56 | }
57 | */
58 | }
59 |
60 | void updateState(int projectSelect)
61 | {
62 | /*_items.
63 |
64 | _projects = projects;*/
65 | _projectSelect = projectSelect;
66 | }
67 |
68 |
69 | private:
70 | string[] _prettyName;
71 | Project[] _projects;
72 | int _projectSelect;
73 | int _cameraY;
74 |
75 | ProjectItemPanel[] _items;
76 | }
77 |
78 | class ProjectItemPanel : UIElement
79 | {
80 | Project _project;
81 |
82 | this(UIContext context, Project project)
83 | {
84 | super(context);
85 | _project = project;
86 | }
87 | }
--------------------------------------------------------------------------------
/source/dido/panel/westpanel.d:
--------------------------------------------------------------------------------
1 | module dido.panel.westpanel;
2 |
3 | import std.path : baseName;
4 |
5 | import dido.buffer.buffer;
6 | import dido.gui;
7 | import dido.engine;
8 |
9 | import dido.panel.solutionpanel,
10 | dido.panel.textarea,
11 | dido.panel.outputpanel;
12 |
13 | class WestPanel : UIElement
14 | {
15 | public:
16 |
17 | this(UIContext context, SolutionPanel solutionPanel, OutputPanel outputPanel)
18 | {
19 | super(context);
20 | addChild(solutionPanel);
21 | addChild(outputPanel);
22 | }
23 |
24 | override void reflow(box2i availableSpace)
25 | {
26 | _position = availableSpace;
27 |
28 | int margin = 2;
29 |
30 | box2i spaceForSolutionPanel = availableSpace;
31 | spaceForSolutionPanel.max.y = cast(int)(0.5 + spaceForSolutionPanel.min.y * 0.25 + 0.75 * spaceForSolutionPanel.max.y);
32 | box2i spaceForOutputPanel = availableSpace;
33 | spaceForOutputPanel.min.y = spaceForSolutionPanel.max.y;
34 |
35 | spaceForSolutionPanel.min.x += margin;
36 | spaceForSolutionPanel.max.x -= margin;
37 | spaceForSolutionPanel.min.y += margin;
38 | spaceForSolutionPanel.max.y -= margin / 2;
39 |
40 | spaceForOutputPanel.min.x += margin;
41 | spaceForOutputPanel.max.x -= margin;
42 | spaceForOutputPanel.min.y += margin / 2;
43 |
44 | child(0).reflow(spaceForSolutionPanel);
45 | child(1).reflow(spaceForOutputPanel);
46 | }
47 |
48 | override void preRender(SDL2Renderer renderer)
49 | {
50 | renderer.setColor(34, 34, 34, 255);
51 | renderer.fillRect(0, 0, _position.width, _position.height);
52 | }
53 |
54 | private:
55 | SolutionPanel _solutionPanel;
56 | OutputPanel _outputPanel;
57 | }
58 |
59 | class EastPanel : UIElement
60 | {
61 | public:
62 | this(UIContext context, DidoEngine engine, TextArea textArea)
63 | {
64 | _textArea = textArea;
65 | _engine = engine;
66 | super(context);
67 | addChild(textArea);
68 | }
69 |
70 | override bool onMousePreClick(int x, int y, int button, bool isDoubleClick)
71 | {
72 | _engine.leaveCommandLineMode();
73 | return false; // let the event bubble anyway
74 | }
75 |
76 | private:
77 | TextArea _textArea;
78 | DidoEngine _engine;
79 | }
--------------------------------------------------------------------------------
/source/dido/panel/outputpanel.d:
--------------------------------------------------------------------------------
1 | module dido.panel.outputpanel;
2 |
3 | import dido.gui;
4 |
5 | enum LineType
6 | {
7 | ERROR,
8 | SUCCESS,
9 | COMMAND,
10 | RESULT,
11 | EXTERNAL,
12 | }
13 |
14 | struct LineOutput
15 | {
16 | LineType type;
17 | dstring msg;
18 | }
19 |
20 | class OutputPanel : UIElement
21 | {
22 | public:
23 |
24 | enum textPadding = 4;
25 |
26 | this(UIContext context)
27 | {
28 | super(context);
29 | _maxHistory = 1000;
30 | }
31 |
32 | override void reflow(box2i availableSpace)
33 | {
34 | _position = availableSpace;
35 | }
36 |
37 | override void preRender(SDL2Renderer renderer)
38 | {
39 | renderer.setColor(8, 9, 10, 255);
40 | renderer.fillRect(0, 0, _position.width, _position.height);
41 | /*
42 | renderer.setColor(30, 30, 30, 255);
43 | renderer.drawRect(0, 0, _position.width, _position.height);
44 | */
45 |
46 | int fh = font.charHeight();
47 |
48 | // set camera automatically
49 | if (4 + _log.length * fh < _position.height)
50 | _cameraY = 0;
51 | else
52 | _cameraY = 4 + cast(int) _log.length * fh - _position.height;
53 |
54 | for (int i = 0; i < cast(int) (_log.length); ++i)
55 | {
56 | final switch(_log[i].type) with (LineType)
57 | {
58 | case ERROR: font.setColor(138, 36, 26); break;
59 | case SUCCESS: font.setColor(66, 137, 45); break;
60 | case EXTERNAL: font.setColor(128, 128, 128); break;
61 | case COMMAND: font.setColor(175, 176, 112); break;
62 | case RESULT: font.setColor(90, 168, 168); break;
63 | }
64 | int textX = textPadding;
65 | int textY = -_cameraY + textPadding + i * fh;
66 | font.renderString!dstring(_log[i].msg, textX, textY);
67 | }
68 | }
69 |
70 | void clearLog()
71 | {
72 | _log.length = 0;
73 | }
74 |
75 | void log(LineOutput lo)
76 | {
77 | _log ~= lo;
78 | if (_log.length > _maxHistory)
79 | {
80 | size_t toStrip = _log.length - _maxHistory;
81 | _log = _log[toStrip..$];
82 | }
83 |
84 | }
85 |
86 | private:
87 | size_t _maxHistory;
88 | LineOutput[] _log;
89 | int _cameraY;
90 | }
91 |
--------------------------------------------------------------------------------
/source/dido/gui/context.d:
--------------------------------------------------------------------------------
1 | module dido.gui.context;
2 |
3 | import core.stdc.stdlib;
4 | public import gfm.math;
5 | public import gfm.sdl2;
6 | import dido.pngload;
7 | public import dido.gui.font;
8 | import std.file;
9 |
10 | import dido.gui.element;
11 |
12 | class UIContext
13 | {
14 | public:
15 | this(SDL2 sdl2_, SDL2Renderer renderer_, Font font_)
16 | {
17 | renderer = renderer_;
18 | font = font_;
19 |
20 |
21 | sdl2 = sdl2_;
22 | }
23 |
24 | ~this()
25 | {
26 | foreach(t; _textures)
27 | t.destroy();
28 |
29 | foreach(s; _surfaces)
30 | s.destroy();
31 | }
32 |
33 | void addImage(string name, immutable(ubyte[]) data)
34 | {
35 | _surfaces[name] = loadImage(sdl2, data);
36 | auto texture = new SDL2Texture(renderer, _surfaces[name]);
37 | texture.setAlphaMod(255);
38 | texture.setColorMod(255, 255, 255);
39 | _textures[name] = texture;
40 | }
41 |
42 | SDL2 sdl2;
43 | SDL2Renderer renderer;
44 | Font font;
45 | UIElement dragged = null; // current dragged element
46 |
47 | SDL2Texture image(string name)
48 | {
49 | return _textures[name];
50 | }
51 |
52 | void beginDragging(UIElement element)
53 | {
54 | stopDragging();
55 |
56 | // Uncomment this once SDL_CaptureMouse is in Derelict
57 | // SDL_CaptureMouse(SDL_TRUE);
58 |
59 | dragged = element;
60 | dragged.onBeginDrag();
61 | }
62 |
63 | void stopDragging()
64 | {
65 | if (dragged !is null)
66 | {
67 | dragged.onStopDrag();
68 | dragged = null;
69 |
70 | // Uncomment this once SDL_CaptureMouse is in Derelict
71 | // SDL_CaptureMouse(SDL_FALSE);
72 | }
73 | }
74 |
75 | private:
76 | SDL2Surface[string] _surfaces;
77 | SDL2Texture[string] _textures;
78 |
79 | SDL2Surface loadImage(SDL2 sdl2, immutable(ubyte[]) imageData)
80 | {
81 | void[] data = cast(void[])imageData;
82 | int width, height, components;
83 |
84 | ubyte* decoded = stbi_load_png_from_memory(data, width, height, components, 4);
85 | scope(exit) free(decoded);
86 |
87 | // stb_image guarantees that ouput will always have 4 components when asked
88 | SDL2Surface loaded = new SDL2Surface(sdl2, decoded, width, height, 32, 4 * width,
89 | 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
90 |
91 | SDL2Surface cloned = loaded.clone(); // to gain pixel ownership
92 | loaded.destroy();
93 | return cloned;
94 | }
95 | }
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/source/dido/panel/commandlinepanel.d:
--------------------------------------------------------------------------------
1 | module dido.panel.commandlinepanel;
2 |
3 | import gfm.math;
4 | import dido.gui;
5 | import dido.colors;
6 | import dido.engine;
7 |
8 | import dido.panel.textarea;
9 | import dido.buffer.buffer;
10 |
11 | static immutable vec3i statusGreen = vec3i(0, 255, 0);
12 | static immutable vec3i statusYellow = vec3i(255, 255, 0);
13 | static immutable vec3i statusRed = vec3i(255, 0, 0);
14 |
15 |
16 | class CommandLinePanel : UIElement
17 | {
18 | public:
19 |
20 | int margin = 4;
21 |
22 | this(UIContext context)
23 | {
24 | super(context);
25 | _statusLine = "";
26 | _buffer = new Buffer();
27 | _textArea = new TextArea(context, margin, false, false, rgba(15, 14, 14, 0));
28 | addChild(_textArea);
29 | }
30 |
31 | override void reflow(box2i availableSpace)
32 | {
33 | _position = availableSpace;
34 | _position.min.y = availableSpace.max.y - (2 * margin + charHeight);
35 |
36 | availableSpace = _position;
37 | availableSpace.min.x += charWidth;
38 | _textArea.reflow(availableSpace);
39 | }
40 |
41 | override void preRender(SDL2Renderer renderer)
42 | {
43 | renderer.setColor(15, 14, 14, 255);
44 | renderer.fillRect(0, 0, _position.width, _position.height);
45 |
46 | {
47 | // commandline bar at bottom
48 |
49 | int textPosx = 4 + charWidth;
50 | int textPosy = 4;
51 |
52 | if (_commandLineMode)
53 | {
54 | font.setColor(255, 255, 0, 255);
55 | font.renderString(":", 4, 4);
56 | }
57 | else
58 | {
59 | // Write status line
60 | font.setColor(_statusColor.r, _statusColor.g, _statusColor.b, 255);
61 | font.renderString(_statusLine, textPosx, textPosy);
62 | }
63 | }
64 | }
65 |
66 | void updateMode(bool commandLineMode)
67 | {
68 | _commandLineMode = commandLineMode;
69 | _textArea.setVisible(commandLineMode);
70 | }
71 |
72 | void updateCursorState(bool showCursors)
73 | {
74 | _textArea.setState(_buffer, showCursors);
75 | }
76 |
77 | dstring getCommandLine()
78 | {
79 | return _buffer.getContent();
80 | }
81 |
82 | Buffer buffer()
83 | {
84 | return _buffer;
85 | }
86 |
87 | TextArea textArea()
88 | {
89 | return _textArea;
90 | }
91 |
92 | void updateEngine(DidoEngine engine)
93 | {
94 | _engine = engine;
95 | }
96 |
97 | override bool onMousePreClick(int x, int y, int button, bool isDoubleClick)
98 | {
99 | _engine.enterCommandLineMode();
100 | return false; // let the event bubble anyway
101 | }
102 |
103 |
104 | private:
105 | bool _commandLineMode;
106 | TextArea _textArea;
107 | Buffer _buffer;
108 | DidoEngine _engine;
109 |
110 | dstring _statusLine;
111 | vec3i _statusColor;
112 | }
113 |
--------------------------------------------------------------------------------
/source/main.d:
--------------------------------------------------------------------------------
1 | module app;
2 |
3 | import std.algorithm;
4 | import std.file;
5 | import std.array;
6 | import std.typecons;
7 | import std.stdio;
8 | import std.string;
9 | import std.path;
10 |
11 | import dido.app;
12 | import dido.config;
13 |
14 | void usage()
15 | {
16 | writefln("Dido: a text editor for D projects.");
17 | writefln("usage: dido ");
18 | writefln("");
19 | writefln("-h / --help: show this help");
20 | writefln(": add this DUB project");
21 | }
22 |
23 | void main(string[] args)
24 | {
25 | try
26 | {
27 |
28 | string[] dubFiles;
29 | bool showHelp = false;
30 |
31 | // parse arguments
32 | for (int i = 1; i < cast(int)(args.length); ++i)
33 | {
34 | string arg = args[i];
35 | if (arg == "-h" || arg == "--help")
36 | {
37 | showHelp = true;
38 | }
39 | else
40 | {
41 | dubFiles ~= args[i];
42 | }
43 | }
44 |
45 | if (showHelp)
46 | {
47 | usage();
48 | return;
49 | }
50 |
51 | if (dubFiles.length == 0)
52 | {
53 | dubFiles ~= ".";
54 | }
55 |
56 | // make all paths absolute
57 | for (int i = 0; i < cast(int)(dubFiles.length); ++i)
58 | {
59 | dubFiles[i] = buildNormalizedPath(absolutePath(dubFiles[i]));
60 | }
61 |
62 | // if given directories, find DUB files recursively
63 | for (int i = 0; i < cast(int)(dubFiles.length); ++i)
64 | {
65 | if (std.file.isDir(dubFiles[i]))
66 | {
67 | auto newFiles = filter!`endsWith(a.name, "dub.json") || endsWith(a.name, "package.json")`(dirEntries(dubFiles[i], SpanMode.shallow))
68 | .map!`a.name`.map!`std.path.absolutePath(a)`;
69 | dubFiles = dubFiles ~ newFiles.array;
70 | }
71 |
72 | }
73 |
74 | // remove all directories and duplicates
75 | dubFiles = filter!`!std.file.isDir(a)`(dubFiles).uniq.array;
76 |
77 | // check that there is anything left
78 | if (dubFiles.length == 0)
79 | {
80 | throw new Exception("Didn't found any package.json or dub.json");
81 | }
82 |
83 | // check file names and that they exist
84 | for (int i = 0; i < cast(int)(dubFiles.length); ++i)
85 | {
86 | string s = baseName(dubFiles[i]);
87 | if (s != "dub.json" && s != "package.json")
88 | throw new Exception(format("File '%s' should be named package.json or dub.json"));
89 |
90 | if (!exists(dubFiles[i]))
91 | throw new Exception(format("File '%s' does not exist"));
92 | }
93 |
94 | DidoConfig config = new DidoConfig;
95 |
96 | auto app = scoped!App(config, dubFiles);
97 | app.mainLoop();
98 |
99 | }
100 | catch(Exception e)
101 | {
102 | writefln("error: %s", e.msg);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/source/dido/gui/button.d:
--------------------------------------------------------------------------------
1 | module dido.gui.button;
2 |
3 | import std.algorithm;
4 |
5 | import gfm.math;
6 | import dido.gui;
7 |
8 | class UIButton : UIElement
9 | {
10 | public:
11 |
12 | this(UIContext context, dstring label, string icon = null)
13 | {
14 | super(context);
15 | _label = label;
16 |
17 | _paddingW = 8;
18 | _paddingH = 4;
19 | _icon = icon;
20 | _iconWidth = 0;
21 | _iconHeight = 0;
22 | if (_icon !is null)
23 | {
24 | _iconImage = context.image(icon);
25 | _iconWidth = _iconImage.width();
26 | _iconHeight = _iconImage.height();
27 | }
28 | }
29 |
30 | enum marginIcon = 6;
31 |
32 | override void reflow(box2i availableSpace)
33 | {
34 | int width = 2 * _paddingW + cast(int) _label.length * font.charWidth;
35 | if (_icon !is null)
36 | width += marginIcon + _iconWidth;
37 | int height = 2 * _paddingH + font.charHeight;
38 | _position = box2i(availableSpace.min.x, availableSpace.min.y, availableSpace.min.x + width, availableSpace.min.y + height);
39 | }
40 |
41 | override void preRender(SDL2Renderer renderer)
42 | {
43 | if (isMouseOver())
44 | {
45 | renderer.setColor(30, 27, 27, 255);
46 | renderer.fillRect(1, 1, _position.width - 2, _position.height -2);
47 | }
48 |
49 | if (isMouseOver())
50 | renderer.setColor(70, 67, 67, 255);
51 | else
52 | renderer.setColor(30, 27, 27, 255);
53 |
54 | renderer.drawRect(0, 0, _position.width, _position.height);
55 |
56 | if (isMouseOver())
57 | font.setColor(255, 255, 200);
58 | else
59 | font.setColor(200, 200, 200);
60 |
61 | dstring textChoice = _label;
62 | int heightOfText = font.charHeight;
63 | int widthOfTextPlusIcon = font.charWidth * cast(int) textChoice.length;
64 | if (_icon !is null)
65 | widthOfTextPlusIcon += marginIcon + _iconWidth;
66 |
67 | int iconX = 1 + (position.width - widthOfTextPlusIcon) / 2;
68 | int textX = iconX;
69 | if (_icon !is null)
70 | {
71 | textX += marginIcon + _iconWidth;
72 | renderer.copy(_iconImage, iconX, 1 + (position.height - _iconHeight) / 2);
73 | }
74 | font.renderString(textChoice, textX, 1 + (position.height - heightOfText) / 2);
75 | }
76 |
77 | private:
78 | dstring _label;
79 | int _paddingW;
80 | int _paddingH;
81 | string _icon;
82 | int _iconWidth;
83 | int _iconHeight;
84 | SDL2Texture _iconImage;
85 | }
86 |
87 | class UIImage : UIElement
88 | {
89 | public:
90 | this(UIContext context, string imageName)
91 | {
92 | super(context);
93 | _tex = context.image(imageName);
94 | _width = _tex.width();
95 | _height = _tex.height();
96 | }
97 |
98 | override void preRender(SDL2Renderer renderer)
99 | {
100 | renderer.copy(_tex, 0, 0);
101 | }
102 |
103 | override void reflow(box2i availableSpace)
104 | {
105 | _position = availableSpace;
106 |
107 | // TODO leave this logic out of UIImage
108 | _position.min.x = _position.max.x - _width;
109 | _position.min.y = _position.max.y - _height;
110 | }
111 |
112 | private:
113 | SDL2Texture _tex;
114 | int _width;
115 | int _height;
116 | }
--------------------------------------------------------------------------------
/source/dido/project.d:
--------------------------------------------------------------------------------
1 | module dido.project;
2 |
3 | import std.path;
4 | import std.string;
5 | import std.file;
6 | import std.json;
7 | import std.process;
8 |
9 | import dido.buffer.buffer;
10 |
11 | // Model
12 | class Project
13 | {
14 | private:
15 | string _absPath; // path of dub.json
16 |
17 | string _mainPackage;
18 |
19 | Buffer[] _buffers;
20 | int _bufferSelect;
21 |
22 | ProjectPackage[] _packages;
23 |
24 | public:
25 | this(string absPath)
26 | {
27 | _absPath = absPath;
28 | getDubDescription();
29 |
30 | foreach (ProjectPackage projectPackage; _packages)
31 | {
32 | foreach (ref path; projectPackage.files)
33 | {
34 | Buffer buf = new Buffer(path);
35 | _buffers ~= buf;
36 | }
37 | }
38 |
39 | // create an empty buffer if no file provided
40 | if (_buffers.length == 0)
41 | {
42 | Buffer buf = new Buffer;
43 | _buffers ~= buf;
44 | }
45 |
46 | _bufferSelect = 0;
47 | }
48 |
49 | int numBuffers()
50 | {
51 | return cast(int)(_buffers.length);
52 | }
53 |
54 | // change current edited buffer, but not the project
55 | void setCurrentBuffer(int bufferSelect)
56 | {
57 | _bufferSelect = bufferSelect;
58 | }
59 |
60 | void nextBuffer()
61 | {
62 | setCurrentBuffer( (_bufferSelect + 1) % cast(int) _buffers.length );
63 | }
64 |
65 | void previousBuffer()
66 | {
67 | setCurrentBuffer( (_bufferSelect + cast(int) _buffers.length - 1) % cast(int) _buffers.length );
68 | }
69 |
70 | int bufferSelect(int x)
71 | {
72 | return _bufferSelect = x;
73 | }
74 |
75 | int bufferSelect()
76 | {
77 | return _bufferSelect;
78 | }
79 |
80 | ref Buffer[] buffers()
81 | {
82 | return _buffers;
83 | }
84 |
85 | Buffer currentBuffer()
86 | {
87 | return _buffers[_bufferSelect];
88 | }
89 |
90 | private:
91 |
92 | void getDubDescription()
93 | {
94 | // change directory
95 | string oldDir = getcwd();
96 | chdir(dirName(_absPath));
97 | scope(exit)
98 | chdir(oldDir);
99 |
100 | auto dubResult = execute(["dub", "describe"]);
101 |
102 | if (dubResult.status != 0)
103 | throw new Exception(format("dub returned %s", dubResult.status));
104 |
105 | JSONValue description = parseJSON(dubResult.output);
106 |
107 | try
108 | {
109 | _mainPackage = description["mainPackage"].str;
110 | }
111 | catch(Exception e)
112 | {
113 | _mainPackage = description["rootPackage"].str;
114 | }
115 |
116 | foreach (pack; description["packages"].array())
117 | {
118 | ProjectPackage projectPackage = new ProjectPackage;
119 |
120 | projectPackage.name = pack["name"].str;
121 | projectPackage.absPath = pack["path"].str;
122 |
123 | foreach (file; pack["files"].array())
124 | {
125 | string filepath = file["path"].str();
126 |
127 | // only add files dido can render
128 | if (filepath.endsWith(".d") || filepath.endsWith(".json") || filepath.endsWith(".res"))
129 | {
130 | projectPackage.files ~= buildPath(projectPackage.absPath, filepath);
131 | }
132 | }
133 |
134 | _packages ~= projectPackage;
135 | }
136 | }
137 | }
138 |
139 |
140 | class ProjectPackage
141 | {
142 | string absPath;
143 | string name;
144 | string[] files;
145 | }
--------------------------------------------------------------------------------
/source/dido/gui/combobox.d:
--------------------------------------------------------------------------------
1 | module dido.gui.combobox;
2 |
3 | import std.algorithm;
4 |
5 | import gfm.math;
6 | import dido.gui;
7 |
8 | class ComboBox : UIElement
9 | {
10 | public:
11 |
12 | this(UIContext context, dstring[] labels, dstring[] choices, string icon = null)
13 | {
14 | super(context);
15 | _labels = labels;
16 | _choices = choices;
17 | assert(_labels.length == _choices.length);
18 | _select = -1;
19 |
20 | _paddingW = 8;
21 | _paddingH = 4;
22 | setSelectedChoice(0);
23 |
24 | _icon = icon;
25 | _iconWidth = 0;
26 | _iconHeight = 0;
27 | if (_icon !is null)
28 | {
29 | _iconImage = context.image(icon);
30 | _iconWidth = _iconImage.width();
31 | _iconHeight = _iconImage.height();
32 | }
33 | }
34 |
35 | enum marginIcon = 6;
36 |
37 | final void setSelectedChoice(int n)
38 | {
39 | if (_select != n)
40 | {
41 | _select = n;
42 | onChoice(n);
43 | }
44 | }
45 |
46 | // Called whenever a combobox is selected.
47 | void onChoice(int n)
48 | {
49 | // do nothing
50 | }
51 |
52 | override void reflow(box2i availableSpace)
53 | {
54 | int width = 2 * _paddingW + longestStringLength() * font.charWidth;
55 | if (_icon !is null)
56 | width += marginIcon + _iconWidth;
57 | int height = 2 * _paddingH + font.charHeight;
58 |
59 | _position = box2i(availableSpace.min.x, availableSpace.min.y, availableSpace.min.x + width, availableSpace.min.y + height);
60 | }
61 |
62 | override void preRender(SDL2Renderer renderer)
63 | {
64 | if (isMouseOver())
65 | {
66 | renderer.setColor(30, 27, 27, 255);
67 | renderer.fillRect(1, 1, _position.width - 2, _position.height -2);
68 | }
69 |
70 | if (isMouseOver())
71 | renderer.setColor(70, 67, 67, 255);
72 | else
73 | renderer.setColor(30, 27, 27, 255);
74 |
75 | renderer.drawRect(0, 0, _position.width, _position.height);
76 |
77 | if (isMouseOver())
78 | font.setColor(255, 255, 200);
79 | else
80 | font.setColor(200, 200, 200);
81 |
82 | dstring textChoice = label(_select);
83 | int heightOfText = font.charHeight;
84 | int widthOfText = font.charWidth * cast(int) textChoice.length;
85 | int iconX = _paddingW;
86 | int availableWidthForText = position.width - ((_icon is null) ? 0 : (marginIcon + _iconWidth));
87 | int textX = 1 + (availableWidthForText - widthOfText) / 2;
88 | if (_icon !is null)
89 | {
90 | textX += marginIcon + _iconWidth;
91 | renderer.copy(_iconImage, iconX, 1 + (position.height - _iconHeight) / 2);
92 | }
93 | font.renderString(textChoice, textX, 1 + (position.height - heightOfText) / 2);
94 | }
95 |
96 | override bool onMousePostClick(int x, int y, int button, bool isDoubleClick)
97 | {
98 | if (_choices.length == 0)
99 | return false;
100 | setSelectedChoice((_select + 1) % cast(int) _choices.length);
101 | return true;
102 | }
103 |
104 | // Called when mouse move over this Element.
105 | override void onMouseMove(int x, int y, int dx, int dy)
106 | {
107 | }
108 |
109 | // Called when mouse enter this Element.
110 | override void onMouseExit()
111 | {
112 | }
113 |
114 | dstring choice(int n)
115 | {
116 | return _choices[n];
117 | }
118 |
119 | dstring label(int n)
120 | {
121 | return _labels[n];
122 | }
123 |
124 | private:
125 |
126 | int _select;
127 | int _paddingW;
128 | int _paddingH;
129 |
130 | string _icon;
131 | int _iconWidth;
132 | int _iconHeight;
133 | SDL2Texture _iconImage;
134 |
135 | dstring[] _labels;
136 | dstring[] _choices;
137 |
138 | int longestStringLength()
139 | {
140 | int maximum = 0;
141 | foreach(ref dstring c; _labels)
142 | {
143 | if (maximum < cast(int) c.length)
144 | maximum = cast(int) c.length;
145 | }
146 | return maximum;
147 | }
148 | }
--------------------------------------------------------------------------------
/fonts/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 |
5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/source/dido/builder.d:
--------------------------------------------------------------------------------
1 | module dido.builder;
2 |
3 | import core.thread;
4 |
5 | import std.array;
6 | import std.conv;
7 | import std.string;
8 | import std.process;
9 |
10 |
11 | import dido.engine;
12 | import dido.panel.outputpanel;
13 |
14 | class Builder
15 | {
16 | public:
17 | this(DidoEngine engine)
18 | {
19 | _engine = engine;
20 | }
21 |
22 | void startBuild(string compiler, string arch, string build)
23 | {
24 | stopBuild();
25 | if (_state != State.initial)
26 | return;
27 |
28 | _state = State.building;
29 | _buildThread = new BuildThread(this, BuildAction.build, compiler, arch, build);
30 | _buildThread.start();
31 | }
32 |
33 | void startRun(string compiler, string arch, string build)
34 | {
35 | stopBuild();
36 | if (_state != State.initial)
37 | return;
38 |
39 | _state = State.running;
40 | _buildThread = new BuildThread(this, BuildAction.run, compiler, arch, build);
41 | _buildThread.start();
42 | }
43 |
44 | void startTest(string compiler, string arch, string build)
45 | {
46 | stopBuild();
47 | if (_state != State.initial)
48 | return;
49 |
50 | _state = State.building;
51 | _buildThread = new BuildThread(this, BuildAction.test, compiler, arch, build);
52 | _buildThread.start();
53 | }
54 |
55 | void stopBuild()
56 | {
57 | if (_state == State.initial)
58 | return;
59 |
60 | _state = State.initial;
61 | _buildThread.signalStop();
62 | _buildThread.join();
63 | }
64 |
65 | private:
66 | enum State
67 | {
68 | initial,
69 | building,
70 | running
71 | }
72 | shared State _state = State.initial;
73 |
74 | DidoEngine _engine;
75 |
76 | BuildThread _buildThread;
77 | }
78 |
79 | enum BuildAction
80 | {
81 | build,
82 | test,
83 | run
84 | }
85 |
86 | class BuildThread : Thread
87 | {
88 | this(Builder builder, BuildAction action, string compiler, string arch, string build)
89 | {
90 | _action = action;
91 | _builder = builder;
92 |
93 | _compiler = compiler;
94 | _arch = arch;
95 | _build = build;
96 | super( &run );
97 | }
98 |
99 | void signalStop()
100 | {
101 | synchronized(this)
102 | _stop = true;
103 | }
104 |
105 | private:
106 | Builder _builder;
107 | BuildAction _action;
108 | bool _stop = false;
109 | string _compiler;
110 | string _arch;
111 | string _build;
112 |
113 | void run()
114 | {
115 | string command;
116 | final switch(_action) with (BuildAction)
117 | {
118 | case BuildAction.run: command = "run"; break;
119 | case BuildAction.build: command = "build"; break;
120 | case BuildAction.test: command = "test"; break;
121 | }
122 |
123 | auto commands = ["dub", command, "--compiler", _compiler, "--arch", _arch, "--build", _build];
124 | string asOneLine = "$" ~ std.array.join(commands, " ");
125 |
126 | _builder._engine.logMessage(LineType.COMMAND, to!dstring(asOneLine));
127 |
128 |
129 | auto pipes = pipeProcess(["dub", command], Redirect.stdout | Redirect.stderr);
130 | scope(exit)
131 | {
132 | int exitCode = wait(pipes.pid);
133 | if (exitCode == 0)
134 | _builder._engine.logMessage(LineType.RESULT, "Done."d);
135 | else
136 | {
137 | dstring msg = to!dstring(format("dub returned %s", exitCode));
138 | _builder._engine.logMessage(LineType.ERROR, msg);
139 | }
140 | }
141 |
142 | // pipe stdout to output window
143 | foreach (line; pipes.stdout.byLine)
144 | {
145 | synchronized // polling exit condition
146 | {
147 | if (_stop)
148 | {
149 | _builder._engine.greenMessage("Build was interrupted.");
150 | return;
151 | }
152 | }
153 | _builder._engine.logMessage(LineType.EXTERNAL, to!dstring(line));
154 | }
155 |
156 | // pipe stderr to output window
157 | foreach (line; pipes.stderr.byLine)
158 | {
159 | synchronized // polling exit condition
160 | {
161 | if (_stop)
162 | {
163 | _builder._engine.greenMessage("Build was interrupted.");
164 | return;
165 | }
166 | }
167 | _builder._engine.logMessage(LineType.EXTERNAL, to!dstring(line));
168 | }
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/source/dido/buffer/bufferiterator.d:
--------------------------------------------------------------------------------
1 | module dido.buffer.bufferiterator;
2 |
3 | import dido.buffer.buffer;
4 | import dido.buffer.selection;
5 |
6 |
7 | struct Cursor
8 | {
9 | public:
10 | int line = 0;
11 | int column = 0;
12 |
13 | int opCmp(ref const(Cursor) other) pure const nothrow
14 | {
15 | if (line != other.line)
16 | return line - other.line;
17 | if (column != other.column)
18 | return column - other.column;
19 | return 0;
20 | }
21 | }
22 |
23 |
24 | struct BufferIterator
25 | {
26 | private:
27 | Buffer _buffer = null;
28 | Cursor _cursor = Cursor(0, 0);
29 | public:
30 |
31 | this(Buffer buffer, Cursor cursor)
32 | {
33 | _buffer = buffer;
34 | _cursor = cursor;
35 | assert(_buffer !is null);
36 | }
37 |
38 | ref inout(Cursor) cursor() pure inout nothrow
39 | {
40 | return _cursor;
41 | }
42 |
43 | inout(Buffer) buffer() pure inout nothrow
44 | {
45 | return _buffer;
46 | }
47 |
48 | bool opEquals(ref const(BufferIterator) other) pure const nothrow
49 | {
50 | assert(_buffer is other._buffer);
51 | return _cursor == other._cursor;
52 | }
53 |
54 | bool canBeDecremented()
55 | {
56 | BufferIterator begin = _buffer.begin();
57 | return this > begin;
58 | }
59 |
60 | bool canBeIncremented()
61 | {
62 | BufferIterator end = _buffer.end();
63 | return this < end;
64 | }
65 |
66 | bool canGoInDirection(int dx)
67 | {
68 | assert(dx == -1 || dx == 1);
69 | if (dx == 1)
70 | return canBeIncremented();
71 | else
72 | return canBeDecremented();
73 | }
74 |
75 | BufferIterator opUnary(string op)() if (op == "++")
76 | {
77 | _cursor.column += 1;
78 | if (_cursor.column >= _buffer.lineLength(cursor.line))
79 | {
80 | if (_cursor.line + 1 < _buffer.numLines())
81 | {
82 | _cursor.line++;
83 | _cursor.column = 0;
84 | }
85 | else
86 | {
87 | _cursor.column = _buffer.lineLength(_buffer.numLines() - 1);
88 | assert(_cursor.column > 0);
89 | }
90 | }
91 | assert(isValid());
92 | return this;
93 | }
94 |
95 | BufferIterator opUnary(string op)() if (op == "--")
96 | {
97 | assert(_buffer !is null);
98 | _cursor.column -= 1;
99 | if (_cursor.column < 0)
100 | {
101 | if (_cursor.line > 0)
102 | {
103 | _cursor.line--;
104 | _cursor.column = _buffer.lineLength(cursor.line) - 1;
105 |
106 | if (_cursor.column < 0)
107 | _cursor.column = 0;
108 | }
109 | else
110 | {
111 | _cursor.column = 0;
112 | }
113 | }
114 | assert(isValid());
115 | return this;
116 | }
117 |
118 | BufferIterator opOpAssign(string op)(int displacement) if (op == "+")
119 | {
120 | while (displacement > 0 && canBeIncremented())
121 | {
122 | displacement--;
123 | opUnary!("++")();
124 | }
125 | while (displacement < 0 && canBeDecremented())
126 | {
127 | displacement++;
128 | opUnary!("--")();
129 | }
130 | return this;
131 | }
132 |
133 | BufferIterator opOpAssign(string op)(int displacement) if (op == "-")
134 | {
135 | return opOpAssign!"+="(-displacement);
136 | }
137 |
138 | BufferIterator opBinary(string op)(int displacement) if (op == "+")
139 | {
140 | BufferIterator result = this;
141 | return result += displacement;
142 | }
143 |
144 | BufferIterator opBinary(op)(int displacement) if (op == "-")
145 | {
146 | BufferIterator result = this;
147 | result -= displacement;
148 | return result;
149 | }
150 |
151 | int opCmp(ref const(BufferIterator) other) pure const nothrow
152 | {
153 | return cursor.opCmp(other._cursor);
154 | }
155 |
156 | dchar read() pure inout nothrow
157 | {
158 | assert(isValid());
159 | return buffer.line(cursor.line)[cursor.column];
160 | }
161 |
162 | bool isValid() pure inout nothrow
163 | {
164 | if (cursor.line < 0)
165 | return false;
166 | if (cursor.line >= buffer.numLines())
167 | return false;
168 | if (cursor.column < 0)
169 | return false;
170 |
171 | // On last line, it is legal to have the cursor at the end of the line
172 | int lineLength = buffer.lineLength(cursor.line);
173 | if (cursor.column >= lineLength)
174 | {
175 | if ( (cursor.line == buffer.numLines() - 1) && cursor.column == lineLength)
176 | return true;
177 | else
178 | return false;
179 | }
180 | return true;
181 | }
182 |
183 | }
184 |
--------------------------------------------------------------------------------
/source/dido/gui/scrollbar.d:
--------------------------------------------------------------------------------
1 | module dido.gui.scrollbar;
2 |
3 | import std.algorithm;
4 |
5 | import gfm.math;
6 | import dido.gui;
7 |
8 | class ScrollBar : UIElement
9 | {
10 | public:
11 |
12 | this(UIContext context, int thicknessOfFocusBar, int padding, bool vertical)
13 | {
14 | super(context);
15 | _vertical = vertical;
16 | _thicknessOfFocusBar = thicknessOfFocusBar;
17 | _padding = padding;
18 | setProgress(0.45f, 0.55f);
19 | }
20 |
21 | // Called whenever a scrollbar move is done. Override it to change behaviour.
22 | void onScrollChangeMouse(float newProgressStart)
23 | {
24 | // do nothing
25 | }
26 |
27 | int thickness() pure const nothrow
28 | {
29 | return _thicknessOfFocusBar + 2 * _padding;
30 | }
31 |
32 | override void reflow(box2i availableSpace)
33 | {
34 | _position = availableSpace;
35 |
36 | if (_vertical)
37 | {
38 | _position.min.x = _position.max.x - thickness();
39 | }
40 | else
41 | {
42 | _position.min.y = _position.max.y - thickness();
43 | }
44 | }
45 |
46 | override void preRender(SDL2Renderer renderer)
47 | {
48 | // Do not display useless scrollbar
49 | if (_progressStart <= 0.0f && _progressStop >= 1.0f)
50 | return;
51 |
52 | renderer.setColor(0x30, 0x2C, 0x2C, 255);
53 | renderer.fillRect(0, 0, _position.width, _position.height);
54 |
55 | if (isMouseOver())
56 | renderer.setColor(120, 120, 120, 255);
57 | else
58 | renderer.setColor( 80, 80, 80, 255);
59 |
60 | box2i focus = getFocusBox();
61 | roundedRect(renderer, focus);
62 | }
63 |
64 | void roundedRect(SDL2Renderer renderer, box2i b)
65 | {
66 | if (b.height > 2 && b.width > 2)
67 | {
68 | renderer.fillRect(b.min.x + 1, b.min.y , b.width - 2, 1);
69 | renderer.fillRect(b.min.x , b.min.y + 1, b.width , b.height - 2);
70 | renderer.fillRect(b.min.x + 1, b.max.y - 1, b.width - 2, 1);
71 | }
72 | else
73 | renderer.fillRect(b.min.x, b.min.y, b.width, b.height);
74 | }
75 |
76 | void setProgress(float progressStart, float progressStop)
77 | {
78 | _progressStart = gfm.math.clamp!float(progressStart, 0.0f, 1.0f);
79 | _progressStop = gfm.math.clamp!float(progressStop, 0.0f, 1.0f);
80 | if (_progressStop < _progressStart)
81 | _progressStop = _progressStart;
82 | }
83 |
84 | override bool onMousePostClick(int x, int y, int button, bool isDoubleClick)
85 | {
86 | float clickProgress;
87 | if (_vertical)
88 | {
89 | int heightWithoutButton = _position.height;
90 | clickProgress = cast(float)(y) / heightWithoutButton;
91 | }
92 | else
93 | {
94 | int widthWithoutButton = _position.width;
95 | clickProgress = cast(float)(x) / widthWithoutButton;
96 | }
97 |
98 | // now this clickProgress should move the _center_ of the scrollbar to it
99 | float newStartProgress = clickProgress - (_progressStop - _progressStart) * 0.5f;
100 |
101 | onScrollChangeMouse(newStartProgress);
102 | return true;
103 | }
104 |
105 | // Called when mouse move over this Element.
106 | override void onMouseDrag(int x, int y, int dx, int dy)
107 | {
108 | float clickProgress;
109 | if (_vertical)
110 | {
111 | int heightWithoutButton = _position.height;
112 | clickProgress = cast(float)(dy) / heightWithoutButton;
113 | }
114 | else
115 | {
116 | int widthWithoutButton = _position.width;
117 | clickProgress = cast(float)(dx) / widthWithoutButton;
118 | }
119 | onScrollChangeMouse(_progressStart + clickProgress);
120 | }
121 |
122 | private:
123 |
124 | int _thicknessOfFocusBar;
125 | int _padding;
126 |
127 | bool _vertical;
128 | float _progressStart;
129 | float _progressStop;
130 |
131 | box2i getFocusBox()
132 | {
133 | if (_vertical)
134 | {
135 | int iprogressStart = cast(int)(0.5f + _progressStart * (_position.height - 2 * _padding));
136 | int iprogressStop = cast(int)(0.5f + _progressStop * (_position.height - 2 * _padding));
137 | int x = _padding;
138 | int y = iprogressStart + _padding;
139 | return box2i(x, y, x + _position.width - 2 * _padding, y + iprogressStop - iprogressStart);
140 | }
141 | else
142 | {
143 | int iprogressStart = cast(int)(0.5f + _progressStart * (_position.width - 2 * _padding));
144 | int iprogressStop = cast(int)(0.5f + _progressStop * (_position.width - 2 * _padding));
145 | int x = iprogressStart + _padding;
146 | int y = _padding;
147 | return box2i(x, y, x + iprogressStop - iprogressStart, y + _position.height - 2 * _padding);
148 | }
149 | }
150 |
151 | }
--------------------------------------------------------------------------------
/source/dido/panel/menupanel.d:
--------------------------------------------------------------------------------
1 | module dido.panel.menupanel;
2 |
3 | import std.conv;
4 |
5 | import dido.gui;
6 | import dido.app;
7 | import dido.engine;
8 |
9 | class MenuPanel : UIElement
10 | {
11 | this(UIContext context, DidoEngine engine)
12 | {
13 | super(context);
14 |
15 | addChild(new BuildButton(context, engine));
16 | addChild(new RunButton(context, engine));
17 | addChild(new TestButton(context, engine));
18 |
19 | addChild(new CompilerCombo(context, engine));
20 | addChild(new ArchCombo(context, engine));
21 | addChild(new BuildCombo(context, engine));
22 | addChild(new DlangOrgButton(context));
23 | addChild(new DubRegistryButton(context));
24 | }
25 |
26 | override void reflow(box2i availableSpace)
27 | {
28 | _position = availableSpace;
29 | _position.max.y = availableSpace.min.y + 8 + 8 + charHeight;
30 |
31 | int margin = 4;
32 |
33 | availableSpace = _position.shrink(margin);
34 |
35 | availableSpace.min.x += 16 + 6 + margin;
36 |
37 | void reflowChild(int n)
38 | {
39 | child(n).reflow(availableSpace);
40 | availableSpace.min.x = child(n).position.max.x + margin;
41 | }
42 |
43 | reflowChild(0);
44 | reflowChild(1);
45 | reflowChild(2);
46 | reflowChild(3);
47 | reflowChild(4);
48 | reflowChild(5);
49 | reflowChild(6);
50 | }
51 |
52 | override void preRender(SDL2Renderer renderer)
53 | {
54 | renderer.setColor(15, 14, 14, 255);
55 | renderer.fillRect(0, 0, _position.width, _position.height);
56 |
57 | renderer.copy(context.image("dido"), 6, (_position.height - 20) / 2);
58 | }
59 | }
60 |
61 | class BuildCombo : ComboBox
62 | {
63 | this(UIContext context, DidoEngine engine)
64 | {
65 | _engine = engine;
66 | super(context,
67 | [ "debug", "release", "nobounds" ],
68 | [ "debug", "release", "release-nobounds"], "cycle");
69 | }
70 |
71 | override void onChoice(int n)
72 | {
73 | _engine.executeScheme("(define current-build \"" ~ to!string(choice(n)) ~ "\")");
74 | }
75 |
76 | private:
77 | DidoEngine _engine;
78 | }
79 |
80 | class ArchCombo : ComboBox
81 | {
82 | this(UIContext context, DidoEngine engine)
83 | {
84 | _engine = engine;
85 | super(context,
86 | [ "32-bit", "64-bit" ],
87 | [ "x86", "x86_64" ],
88 | "cycle");
89 | }
90 |
91 | override void onChoice(int n)
92 | {
93 | _engine.executeScheme("(define current-arch \"" ~ to!string(choice(n)) ~ "\")");
94 | }
95 | private:
96 | DidoEngine _engine;
97 | }
98 |
99 |
100 | class CompilerCombo : ComboBox
101 | {
102 | this(UIContext context, DidoEngine engine)
103 | {
104 | _engine = engine;
105 | super(context,
106 | [ "DMD", "LDC" ],
107 | [ "DMD", "LDC" ], "cycle");
108 | }
109 |
110 | override void onChoice(int n)
111 | {
112 | _engine.executeScheme("(define current-compiler \"" ~ to!string(choice(n)) ~ "\")");
113 | }
114 | private:
115 | DidoEngine _engine;
116 | }
117 |
118 | class BuildButton : UIButton
119 | {
120 | public:
121 | this(UIContext context, DidoEngine engine)
122 | {
123 | super(context, "Build", "build");
124 | _engine = engine;
125 | }
126 |
127 | override bool onMousePostClick(int x, int y, int button, bool isDoubleClick)
128 | {
129 | _engine.executeScheme("(build current-compiler current-arch current-build)");
130 | return true;
131 | }
132 | private:
133 | DidoEngine _engine;
134 | }
135 |
136 | class TestButton : UIButton
137 | {
138 | public:
139 | this(UIContext context, DidoEngine engine)
140 | {
141 | super(context, "Test", "test");
142 | _engine = engine;
143 | }
144 |
145 | override bool onMousePostClick(int x, int y, int button, bool isDoubleClick)
146 | {
147 | _engine.executeScheme("(build current-compiler current-arch current-build)");
148 | return true;
149 | }
150 | private:
151 | DidoEngine _engine;
152 | }
153 |
154 | class RunButton : UIButton
155 | {
156 | public:
157 | this(UIContext context, DidoEngine engine)
158 | {
159 | super(context, "Run", "run");
160 | _engine = engine;
161 | }
162 |
163 | override bool onMousePostClick(int x, int y, int button, bool isDoubleClick)
164 | {
165 | _engine.executeScheme("(run current-compiler current-arch current-build)");
166 | return true;
167 | }
168 |
169 | private:
170 | DidoEngine _engine;
171 | }
172 |
173 | class DlangOrgButton : UIButton
174 | {
175 | public:
176 | this(UIContext context)
177 | {
178 | super(context, "Home", "dlang");
179 | }
180 |
181 | override bool onMousePostClick(int x, int y, int button, bool isDoubleClick)
182 | {
183 | import std.process;
184 | browse("http://dlang.org/");
185 | return true;
186 | }
187 | }
188 |
189 | class DubRegistryButton : UIButton
190 | {
191 | public:
192 | this(UIContext context)
193 | {
194 | super(context, "Registry", "dub");
195 | }
196 |
197 | override bool onMousePostClick(int x, int y, int button, bool isDoubleClick)
198 | {
199 | import std.process;
200 | browse("http://code.dlang.org/");
201 | return true;
202 | }
203 | }
--------------------------------------------------------------------------------
/source/dido/gui/font.d:
--------------------------------------------------------------------------------
1 | module dido.gui.font;
2 |
3 | import std.file;
4 | import std.conv;
5 | import gfm.sdl2;
6 | import dido.pngload;
7 | import dido.stb_truetype;
8 |
9 | final class Font
10 | {
11 | public:
12 | this(SDL2 sdl2, SDL2Renderer renderer, string fontface, int ptSize)
13 | {
14 | _sdl2 = sdl2;
15 | _renderer = renderer;
16 |
17 |
18 | _fontData = cast(ubyte[])(std.file.read(fontface));
19 | if (0 == stbtt_InitFont(&_font, _fontData.ptr, stbtt_GetFontOffsetForIndex(_fontData.ptr, 0)))
20 | throw new Exception("Coudln't load font " ~ fontface);
21 |
22 | _scaleFactor = stbtt_ScaleForPixelHeight(&_font, ptSize);
23 |
24 | stbtt_GetFontVMetrics(&_font, &_fontAscent, &_fontDescent, &_fontLineGap);
25 |
26 | int ax;
27 | stbtt_GetCodepointHMetrics(&_font, 'A', &ax, null);
28 | _charWidth = cast(int)(0.5 + (ax * _scaleFactor));
29 | _charHeight = cast(int)(0.5 + (_fontAscent - _fontDescent + _fontLineGap) * _scaleFactor);
30 |
31 | _initialized = true;
32 |
33 | _r = 255;
34 | _g = 255;
35 | _b = 255;
36 | _a = 255;
37 | }
38 |
39 | ~this()
40 | {
41 | if (_initialized)
42 | {
43 | foreach (tex; _glyphCache)
44 | tex.destroy();
45 | foreach (tex; _surfaceCache)
46 | tex.destroy();
47 | _initialized = false;
48 | }
49 | }
50 |
51 | SDL2Texture getCharTexture(dchar ch)
52 | {
53 | if (ch == 0)
54 | ch = 0xFFFD;
55 | try
56 | {
57 | if (! (ch in _glyphCache))
58 | {
59 | SDL2Texture tex;
60 | SDL2Surface surf;
61 | makeCharTexture(ch, tex, surf);
62 | _glyphCache[ch] = tex;
63 | _surfaceCache[ch] = surf;
64 | }
65 |
66 | return _glyphCache[ch];
67 | }
68 | catch(SDL2Exception e)
69 | {
70 | if (ch == 0xFFFD)
71 | return null;
72 |
73 | // invalid glyph, return replacement character glyph
74 | return getCharTexture(0xFFFD);
75 | }
76 | }
77 |
78 | void makeCharTexture(dchar ch, out SDL2Texture texture, out SDL2Surface surface)
79 | {
80 | // Generate glyph coverage
81 | int width, height;
82 | int xoff, yoff;
83 | ubyte* glyphBitmap = stbtt_GetCodepointBitmap(&_font, _scaleFactor, _scaleFactor, ch , &width, &height, &xoff, &yoff);
84 |
85 | // Copy to a SDL surface
86 | uint Rmask = 0x00ff0000;
87 | uint Gmask = 0x0000ff00;
88 | uint Bmask = 0x000000ff;
89 | uint Amask = 0xff000000;
90 |
91 | surface = new SDL2Surface(_sdl2, _charWidth, _charHeight, 32, Rmask, Gmask, Bmask, Amask);
92 | {
93 | surface.lock();
94 | scope(exit) surface.unlock();
95 |
96 | // fill with transparent white
97 | for (int i = 0; i < _charHeight; ++i)
98 | {
99 | for (int j = 0; j < _charWidth; ++j)
100 | {
101 | ubyte* dest = &surface.pixels[i * surface.pitch + (j * 4)];
102 | dest[0] = 255;
103 | dest[1] = 255;
104 | dest[2] = 255;
105 | dest[3] = 0;
106 | }
107 | }
108 |
109 | for (int i = 0; i < height; ++i)
110 | {
111 | for (int j = 0; j < width; ++j)
112 | {
113 | ubyte source = glyphBitmap[j + i * width];
114 | int destX = j + xoff;
115 | int destY = i + yoff + cast(int)(0.5 + _fontAscent * _scaleFactor);
116 |
117 | if (destX >= 0 && destX < _charWidth)
118 | {
119 | if (destY >= 0 && destY < _charHeight)
120 | {
121 | ubyte* dest = &surface.pixels[destY * surface.pitch + (destX * 4)];
122 | dest[3] = source; // fully white, but eventually transparent
123 | }
124 | }
125 | }
126 | }
127 | }
128 |
129 | // Free glyph coverage
130 | stbtt_FreeBitmap(glyphBitmap);
131 |
132 | texture = new SDL2Texture(_renderer, surface);
133 | }
134 |
135 | int charWidth() pure const nothrow
136 | {
137 | return _charWidth;
138 | }
139 |
140 | int charHeight() pure const nothrow
141 | {
142 | return _charHeight;
143 | }
144 |
145 | void setColor(int r, int g, int b, int a = 255)
146 | {
147 | _r = r;
148 | _g = g;
149 | _b = b;
150 | _a = a;
151 | }
152 |
153 | void renderString(StringType)(StringType s, int x, int y)
154 | {
155 | foreach(dchar ch; s)
156 | {
157 | SDL2Texture tex = getCharTexture(ch);
158 | tex.setColorMod(_r, _g, _b);
159 | tex.setAlphaMod(_a);
160 | _renderer.copy(tex, x, y);
161 | x += tex.width();
162 | }
163 | }
164 |
165 | void renderChar(dchar ch, int x, int y)
166 | {
167 | SDL2Texture tex = getCharTexture(ch);
168 | tex.setColorMod(_r, _g, _b);
169 | tex.setAlphaMod(_a);
170 | _renderer.copy(tex, x, y);
171 | }
172 |
173 |
174 | private:
175 |
176 | int _r, _g, _b, _a;
177 |
178 | SDL2 _sdl2;
179 |
180 | SDL2Renderer _renderer;
181 | stbtt_fontinfo _font;
182 | ubyte[] _fontData;
183 | int _fontAscent, _fontDescent, _fontLineGap;
184 |
185 | SDL2Texture[dchar] _glyphCache;
186 | SDL2Surface[dchar] _surfaceCache;
187 | int _charWidth;
188 | int _charHeight;
189 | float _scaleFactor;
190 | bool _initialized;
191 | }
192 |
--------------------------------------------------------------------------------
/source/dido/buffer/selection.d:
--------------------------------------------------------------------------------
1 | module dido.buffer.selection;
2 |
3 | import dido.buffer.buffer;
4 | import dido.buffer.bufferiterator;
5 |
6 |
7 | struct Selection
8 | {
9 | BufferIterator anchor; // selection start
10 | BufferIterator edge; // blinking cursor
11 |
12 | this(BufferIterator bothEnds) pure nothrow
13 | {
14 | anchor = bothEnds;
15 | edge = bothEnds;
16 | }
17 |
18 | this(BufferIterator anchor_, BufferIterator edge_) pure nothrow
19 | {
20 | edge = edge_;
21 | anchor = anchor_;
22 | assert(anchor.buffer is edge.buffer);
23 | }
24 |
25 | // start == stop => no selected area
26 |
27 | bool hasSelectedArea() pure const nothrow
28 | {
29 | return anchor != edge;
30 | }
31 |
32 | bool overlaps(Selection other)
33 | {
34 | Selection tsorted = this.sorted();
35 | Selection osorted = other.sorted();
36 | if (osorted.anchor < tsorted.edge && osorted.edge >= tsorted.anchor)
37 | return true;
38 | if (tsorted.anchor < osorted.edge && tsorted.edge >= osorted.anchor)
39 | return true;
40 | return false;
41 | }
42 |
43 | int opCmp(ref Selection other) pure nothrow
44 | {
45 | Selection tsorted = this.sorted();
46 | Selection osorted = other.sorted();
47 | if (tsorted.anchor.cursor.line != osorted.anchor.cursor.line)
48 | return tsorted.anchor.cursor.line - osorted.anchor.cursor.line;
49 | if (tsorted.anchor.cursor.column != osorted.anchor.cursor.column)
50 | return tsorted.anchor.cursor.column - osorted.anchor.cursor.column;
51 |
52 | if (tsorted.edge.cursor.line != osorted.edge.cursor.line)
53 | return tsorted.edge.cursor.line - osorted.edge.cursor.line;
54 | if (tsorted.edge.cursor.column != osorted.edge.cursor.column)
55 | return tsorted.edge.cursor.column - osorted.edge.cursor.column;
56 | return 0;
57 | }
58 |
59 | Selection opOpAssign(string op)(int displacement) if (op == "+")
60 | {
61 | anchor += displacement;
62 | edge += displacement;
63 | return this;
64 | }
65 |
66 | // Returns a sleection with the anchor at the left and the edge at the right
67 | Selection sorted() pure nothrow
68 | {
69 | if (anchor <= edge)
70 | return this;
71 | else
72 | return Selection(edge, anchor);
73 | }
74 |
75 | bool isValid() pure const nothrow
76 | {
77 | return anchor.isValid() && edge.isValid();
78 | }
79 |
80 | void translateByEdit(BufferIterator before, BufferIterator after)
81 | {
82 | Selection tsorted = this.sorted();
83 | assert(before <= tsorted.anchor);
84 |
85 | import std.stdio;
86 | if (before.cursor.line != after.cursor.line)
87 | {
88 | anchor.cursor.line += after.cursor.line - before.cursor.line;
89 | edge.cursor.line += after.cursor.line - before.cursor.line;
90 |
91 | if (anchor.cursor.line == after.cursor.line)
92 | anchor.cursor.column = after.cursor.column;
93 | if (edge.cursor.line == after.cursor.line)
94 | edge.cursor.column = after.cursor.column;
95 | }
96 |
97 | if (before.cursor.line == anchor.cursor.line)
98 | {
99 | int dispBefore = anchor.cursor.column - before.cursor.column;
100 | anchor.cursor.column = after.cursor.column + dispBefore;
101 | }
102 |
103 | if (before.cursor.line == edge.cursor.line)
104 | {
105 | int dispBefore = edge.cursor.column - before.cursor.column;
106 | edge.cursor.column = after.cursor.column + dispBefore;
107 | }
108 | }
109 | }
110 |
111 | class SelectionSet
112 | {
113 | Selection[] selections; // sorted by date
114 |
115 | this(Buffer buffer)
116 | {
117 | // always have a cursor
118 | selections ~= Selection(BufferIterator(buffer, Cursor(0, 0)));
119 | }
120 |
121 | this(Selection[] savedSelections)
122 | {
123 | selections = savedSelections;
124 | }
125 |
126 | void removeDuplicate()
127 | {
128 | import std.algorithm;
129 | import std.array;
130 | selections = selections.uniq.array;
131 | }
132 |
133 | void normalize()
134 | {
135 | // sort selections
136 | import std.algorithm;
137 | sort!("a < b", SwapStrategy.stable)(selections);
138 |
139 | int i = 0;
140 | while (i < cast(int)(selections.length) - 1)
141 | {
142 | if (selections[i].overlaps(selections[i+1]))
143 | {
144 | // merge overlapping selections
145 | Selection A = selections[i].sorted();
146 | Selection B = selections[i+1].sorted();
147 | BufferIterator anchor = A.anchor < B.anchor ? A.anchor : B.anchor;
148 | BufferIterator edge = A.edge > B.edge ? A.edge : B.edge;
149 | selections = selections[0..i] ~ Selection(anchor, edge) ~ selections[i+2..$];
150 | }
151 | else if (selections[i] == selections[i+1])
152 | {
153 | // drop one of two identical selections
154 | selections = selections[0..i] ~ selections[i+1..$];
155 | }
156 | else
157 | ++i;
158 | }
159 | }
160 |
161 | void keepOnlyFirst()
162 | {
163 | selections = selections[0..1];
164 | selections[0].anchor = selections[0].edge;
165 | }
166 |
167 | void keepOnlyEdge()
168 | {
169 | foreach(ref sel; selections)
170 | sel.anchor = sel.edge;
171 | }
172 |
173 | void clampSelections()
174 | {
175 | foreach(ref sel; selections)
176 | {
177 | sel.anchor.cursor = sel.anchor.buffer.clampCursor(sel.anchor.cursor);
178 | sel.edge.cursor = sel.edge.buffer.clampCursor(sel.edge.cursor);
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/source/dido/gui/element.d:
--------------------------------------------------------------------------------
1 | module dido.gui.element;
2 |
3 | public import dido.gui.context;
4 |
5 | class UIElement
6 | {
7 | public:
8 |
9 | this(UIContext context)
10 | {
11 | _context = context;
12 | }
13 |
14 | void close()
15 | {
16 | foreach(child; children)
17 | child.close();
18 | }
19 |
20 | final void render()
21 | {
22 | if (!_visible)
23 | return;
24 |
25 | setViewportToElement();
26 | preRender(_context.renderer);
27 | foreach(ref child; children)
28 | child.render();
29 | setViewportToElement();
30 | postRender(_context.renderer);
31 | }
32 |
33 | /// Meant to be overriden for custom behaviour.
34 | void reflow(box2i availableSpace)
35 | {
36 | // default: span the entire available area, and do the same for children
37 | _position = availableSpace;
38 |
39 | foreach(ref child; children)
40 | child.reflow(availableSpace);
41 | }
42 |
43 | final box2i position()
44 | {
45 | return _position;
46 | }
47 |
48 | final ref UIElement[] children()
49 | {
50 | return _children;
51 | }
52 |
53 | final Font font()
54 | {
55 | return _context.font;
56 | }
57 |
58 | final int charWidth() pure const nothrow
59 | {
60 | return _context.font.charWidth();
61 | }
62 |
63 | final int charHeight() pure const nothrow
64 | {
65 | return _context.font.charHeight();
66 | }
67 |
68 | final UIElement child(int n)
69 | {
70 | return _children[n];
71 | }
72 |
73 | final void addChild(UIElement element)
74 | {
75 | _children ~= element;
76 | }
77 |
78 | // This function is meant to be overriden.
79 | // It should return true if the click is handled.
80 | bool onMousePostClick(int x, int y, int button, bool isDoubleClick)
81 | {
82 | return false;
83 | }
84 |
85 | // This function is meant to be overriden.
86 | // Happens _before_ checking for children collisions.
87 | bool onMousePreClick(int x, int y, int button, bool isDoubleClick)
88 | {
89 | return false;
90 | }
91 |
92 | // This function is meant to be overriden.
93 | // It should return true if the wheel is handled.
94 | bool onMouseWheel(int x, int y, int wheelDeltaX, int wheelDeltaY)
95 | {
96 | return false;
97 | }
98 |
99 | // Called when mouse move over this Element.
100 | void onMouseMove(int x, int y, int dx, int dy)
101 | {
102 | }
103 |
104 | // Called when clicked with left/middle/right button
105 | void onBeginDrag()
106 | {
107 | }
108 |
109 | // Called when mouse drag this Element.
110 | void onMouseDrag(int x, int y, int dx, int dy)
111 | {
112 | }
113 |
114 | // Called once drag is finished
115 | void onStopDrag()
116 | {
117 | }
118 |
119 | // Called when mouse enter this Element.
120 | void onMouseEnter()
121 | {
122 | }
123 |
124 | // Called when mouse enter this Element.
125 | void onMouseExit()
126 | {
127 | }
128 |
129 | // to be called at top-level when the mouse clicked
130 | bool mouseClick(int x, int y, int button, bool isDoubleClick)
131 | {
132 | if (_position.contains(vec2i(x, y)))
133 | {
134 | if(onMousePreClick(x - _position.min.x, y - _position.min.y, button, isDoubleClick))
135 | {
136 | _context.beginDragging(this);
137 | return true;
138 | }
139 | }
140 |
141 | foreach(child; _children)
142 | {
143 | if (child.mouseClick(x, y, button, isDoubleClick))
144 | return true;
145 | }
146 |
147 | if (_position.contains(vec2i(x, y)))
148 | {
149 | if(onMousePostClick(x - _position.min.x, y - _position.min.y, button, isDoubleClick))
150 | {
151 | _context.beginDragging(this);
152 | return true;
153 | }
154 | }
155 |
156 | return false;
157 | }
158 |
159 | // to be called when the mouse is released
160 | final void mouseRelease(int x, int y, int button)
161 | {
162 | _context.stopDragging();
163 | }
164 |
165 | // to be called when the mouse clicked
166 | final bool mouseWheel(int x, int y, int wheelDeltaX, int wheelDeltaY)
167 | {
168 | foreach(child; _children)
169 | {
170 | if (child.mouseWheel(x, y, wheelDeltaX, wheelDeltaY))
171 | return true;
172 | }
173 |
174 | if (_position.contains(vec2i(x, y)))
175 | {
176 | if (onMouseWheel(x - _position.min.x, y - _position.min.y, wheelDeltaX, wheelDeltaY))
177 | return true;
178 | }
179 |
180 | return false;
181 | }
182 |
183 | // to be called when the mouse moved
184 | final void mouseMove(int x, int y, int dx, int dy)
185 | {
186 | if (isDragged)
187 | onMouseDrag(x, y, dx, dy);
188 |
189 | foreach(child; _children)
190 | {
191 | child.mouseMove(x, y, dx, dy);
192 | }
193 |
194 | if (_position.contains(vec2i(x, y)))
195 | {
196 | if (!_mouseOver)
197 | onMouseEnter();
198 | onMouseMove(x - _position.min.x, y - _position.min.y, dx, dy);
199 | _mouseOver = true;
200 | }
201 | else
202 | {
203 | if (_mouseOver)
204 | onMouseExit();
205 | _mouseOver = false;
206 | }
207 | }
208 |
209 | final UIContext context()
210 | {
211 | return _context;
212 | }
213 |
214 | final bool isVisible()
215 | {
216 | return _visible;
217 | }
218 |
219 | final void setVisible(bool visible)
220 | {
221 | _visible = visible;
222 | }
223 |
224 | protected:
225 |
226 | /// Render this element before children.
227 | /// Meant to be overriden.
228 | void preRender(SDL2Renderer renderer)
229 | {
230 | // defaults to nothing
231 | }
232 |
233 | /// Render this element after children elements.
234 | /// Meant to be overriden.
235 | void postRender(SDL2Renderer renderer)
236 | {
237 | // defaults to nothing
238 | }
239 |
240 | box2i _position;
241 |
242 | UIElement[] _children;
243 |
244 | bool _visible = true;
245 |
246 |
247 | final bool isMouseOver() pure const nothrow
248 | {
249 | return _mouseOver;
250 | }
251 |
252 | final bool isDragged() pure const nothrow
253 | {
254 | return _context.dragged is this;
255 | }
256 |
257 |
258 | private:
259 | UIContext _context;
260 |
261 | bool _mouseOver = false;
262 |
263 | final void setViewportToElement()
264 | {
265 | _context.renderer.setViewport(_position.min.x, _position.min.y, _position.width, _position.height);
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/source/dido/app.d:
--------------------------------------------------------------------------------
1 | module dido.app;
2 |
3 | import std.path;
4 | import std.file;
5 | import std.conv;
6 | import std.string;
7 |
8 | import gfm.sdl2;
9 | import gfm.math;
10 |
11 | import dido.buffer.buffer;
12 | import dido.window;
13 | import dido.buffer.selection;
14 | import dido.panel;
15 | import dido.gui;
16 | import dido.engine;
17 | import dido.config;
18 | import dido.colors;
19 |
20 |
21 | final class App
22 | {
23 | public:
24 | this(DidoConfig config, string[] dubPaths)
25 | {
26 | _sdl2 = new SDL2(null);
27 | _window = new Window(_sdl2);
28 |
29 | // font path is relative to application directory
30 | string appDir = dirName(thisExePath());
31 | string fontFile = buildPath(appDir, config.fontFace);
32 | _font = new Font(_sdl2, _window.renderer(), fontFile, config.fontSize);
33 |
34 | _sdl2.startTextInput();
35 |
36 | _uiContext = new UIContext(_sdl2, _window.renderer(), _font);
37 |
38 | {
39 | import dido.panel.images;
40 | addAllImage(_uiContext);
41 | }
42 |
43 | _textArea = new TextArea(_uiContext, 16, true, true, rgba(20, 19, 18, 255));
44 |
45 | _cmdlinePanel = new CommandLinePanel(_uiContext);
46 | _outputPanel = new OutputPanel(_uiContext);
47 |
48 | _engine = new DidoEngine(_sdl2, _window, _textArea, _cmdlinePanel, _outputPanel, dubPaths);
49 |
50 | _solutionPanel = new SolutionPanel(_uiContext, _engine.projects());
51 | _mainPanel = new MainPanel(_uiContext);
52 | _menuPanel = new MenuPanel(_uiContext, _engine);
53 | _westPanel = new WestPanel(_uiContext, _solutionPanel, _outputPanel);
54 | auto eastPanel = new EastPanel(_uiContext, _engine, _textArea);
55 |
56 | // Ugly setter to get the inter-dependent objects live
57 | _cmdlinePanel.updateEngine(_engine);
58 |
59 | _mainPanel.addChild(eastPanel);
60 | _mainPanel.addChild(_westPanel);
61 | _mainPanel.addChild(_cmdlinePanel);
62 | _mainPanel.addChild(_menuPanel);
63 | _mainPanel.addChild(new UIImage(_uiContext, "corner"));
64 |
65 | _needReflow = true;
66 | _needRedraw = true;
67 | }
68 |
69 | ~this()
70 | {
71 | _mainPanel.destroy();
72 | _uiContext.destroy();
73 | _sdl2.stopTextInput();
74 | _font.destroy();
75 | _window.destroy();
76 | _sdl2.destroy();
77 | }
78 |
79 | void mainLoop()
80 | {
81 | uint caretBlinkTime = 530 ; // default value on Win7
82 | uint caretCycleTime = caretBlinkTime * 2; // default value on Win7
83 |
84 | uint lastTime = SDL_GetTicks();
85 |
86 | _timeSinceKeypress = 0;
87 |
88 | bool lastdrawCursors = false;
89 |
90 | bool firstFrame = true;
91 |
92 | while(!_sdl2.wasQuitRequested() && !_engine.finished())
93 | {
94 | uint time = SDL_GetTicks();
95 | uint deltaTime = time - lastTime;
96 | lastTime = time;
97 | _timeSinceKeypress += deltaTime;
98 |
99 | if(!firstFrame)
100 | waitForAnEventThenProcessThemAll();
101 |
102 | firstFrame = false;
103 |
104 | SDL2Renderer renderer = _window.renderer();
105 |
106 | int width = _window.getWidth();
107 | int height = _window.getHeight();
108 | int charWidth = _font.charWidth();
109 | int charHeight = _font.charHeight();
110 |
111 | bool drawCursors = (_timeSinceKeypress % caretCycleTime) < caretBlinkTime;
112 |
113 | if (lastdrawCursors != drawCursors)
114 | _needRedraw = true;
115 |
116 | lastdrawCursors = drawCursors;
117 |
118 | // reflow
119 | if (_needReflow)
120 | {
121 | _solutionPanel.updateState(_engine.projectSelect);
122 | _mainPanel.reflow(box2i(0, 0, width, height));
123 |
124 | _needReflow = false;
125 | }
126 |
127 | // redraw
128 | if (_needRedraw)
129 | {
130 | _cmdlinePanel.updateMode(_engine.isCommandLineMode());
131 | _cmdlinePanel.updateCursorState(drawCursors);
132 | _textArea.setState(_engine.currentEditBuffer(), !_engine.isCommandLineMode() && drawCursors);
133 | renderer.setViewportFull();
134 | _mainPanel.render();
135 | renderer.present();
136 | }
137 | }
138 | }
139 |
140 |
141 | private:
142 |
143 | DidoEngine _engine;
144 | SDL2 _sdl2;
145 | Window _window;
146 |
147 | uint _timeSinceKeypress;
148 |
149 | MainPanel _mainPanel;
150 | MenuPanel _menuPanel;
151 | CommandLinePanel _cmdlinePanel;
152 | SolutionPanel _solutionPanel;
153 | OutputPanel _outputPanel;
154 | WestPanel _westPanel;
155 | TextArea _textArea;
156 | Font _font;
157 | UIContext _uiContext;
158 |
159 | bool _needReflow;
160 | bool _needRedraw;
161 |
162 | void dealWithEvent(ref SDL_Event event)
163 | {
164 | switch (event.type)
165 | {
166 | case SDL_KEYDOWN:
167 | {
168 | _timeSinceKeypress = 0;
169 | auto key = event.key.keysym;
170 | bool alt = (key.mod & KMOD_ALT) != 0;
171 | bool shift = (key.mod & KMOD_SHIFT) != 0;
172 | bool ctrl = (key.mod & KMOD_CTRL) != 0;
173 | string sshift = shift ? "#t" : "#f";
174 |
175 | if (key.sym == SDLK_RETURN && alt)
176 | _engine.executeScheme("(toggle-fullscreen)");
177 | else if (key.sym == SDLK_UP && ctrl && alt)
178 | _engine.executeScheme("(extend-selection-vertical -1)");
179 | else if (key.sym == SDLK_DOWN && ctrl && alt)
180 | _engine.executeScheme("(extend-selection-vertical 1)");
181 | else if (key.sym == SDLK_LEFT && ctrl)
182 | _engine.executeScheme(format("(move-word-left %s)", sshift));
183 | else if (key.sym == SDLK_RIGHT && ctrl)
184 | _engine.executeScheme(format("(move-word-right %s)", sshift));
185 | else if (key.sym == SDLK_ESCAPE)
186 | _engine.executeScheme("(escape)");
187 | else if (key.sym == SDLK_RETURN)
188 | _engine.enter();
189 |
190 | // copy/cut/paste
191 | else if (key.sym == SDLK_c && ctrl)
192 | _engine.executeScheme("(copy)");
193 | else if (key.sym == SDLK_x && ctrl)
194 | _engine.executeScheme("(cut)");
195 | else if (key.sym == SDLK_v && ctrl)
196 | _engine.executeScheme("(paste)");
197 | else if (key.sym == SDLK_COPY)
198 | _engine.executeScheme("(copy)");
199 | else if (key.sym == SDLK_CUT)
200 | _engine.executeScheme("(cut)");
201 | else if (key.sym == SDLK_PASTE)
202 | _engine.executeScheme("(paste)");
203 | else if (key.sym == SDLK_DELETE && shift)
204 | _engine.executeScheme("(cut)");
205 | else if (key.sym == SDLK_INSERT && ctrl)
206 | _engine.executeScheme("(copy)");
207 | else if (key.sym == SDLK_INSERT && shift)
208 | _engine.executeScheme("(paste)");
209 | else if (key.sym == SDLK_LEFT)
210 | _engine.executeScheme(format("(move-horizontal -1 %s)", sshift));
211 | else if (key.sym == SDLK_RIGHT)
212 | _engine.executeScheme(format("(move-horizontal 1 %s)", sshift));
213 | else if (key.sym == SDLK_UP)
214 | _engine.executeScheme(format("(move-vertical -1 %s)", sshift));
215 | else if (key.sym == SDLK_DOWN)
216 | _engine.executeScheme(format("(move-vertical 1 %s)", sshift));
217 | else if (key.sym == SDLK_BACKSPACE)
218 | _engine.executeScheme("(delete-selection #t)");
219 | else if (key.sym == SDLK_DELETE)
220 | _engine.executeScheme("(delete-selection #f)");
221 | else if (key.sym == SDLK_HOME && ctrl)
222 | _engine.executeScheme(format("(move-buffer-start %s)", sshift));
223 | else if (key.sym == SDLK_END && ctrl)
224 | _engine.executeScheme(format("(move-buffer-end %s)", sshift));
225 | else if (key.sym == SDLK_a && ctrl)
226 | _engine.executeScheme("(select-all)");
227 | else if (key.sym == SDLK_END)
228 | _engine.executeScheme(format("(move-line-end %s)", sshift));
229 | else if (key.sym == SDLK_HOME)
230 | _engine.executeScheme(format("(move-line-start %s)", sshift));
231 | else if (key.sym == SDLK_PAGEUP && ctrl)
232 | _engine.executeScheme("(previous-buffer)");
233 | else if (key.sym == SDLK_PAGEDOWN && ctrl)
234 | _engine.executeScheme("(next-buffer)");
235 | else if (key.sym == SDLK_PAGEUP)
236 | _engine.executeScheme(format("(move-vertical (- (visible-lines)) %s)", sshift));
237 | else if (key.sym == SDLK_PAGEDOWN)
238 | _engine.executeScheme(format("(move-vertical (visible-lines) %s)", sshift));
239 | else if (key.sym == SDLK_z && ctrl)
240 | _engine.executeScheme("(undo)");
241 | else if (key.sym == SDLK_y && ctrl)
242 | _engine.executeScheme("(redo)");
243 | else if (key.sym == SDLK_s && ctrl)
244 | _engine.executeScheme("(save)");
245 | else if ( (key.sym == SDLK_F5 && shift) || (key.sym == SDLK_PAUSE && ctrl) )
246 | _engine.executeScheme("(stop-build)");
247 | else if (key.sym == SDLK_F4)
248 | _engine.executeScheme("(build current-compiler current-arch current-build)");
249 | else if (key.sym == SDLK_F5)
250 | _engine.executeScheme("(run current-compiler current-arch current-build)");
251 | else if (key.sym == SDLK_TAB)
252 | _engine.executeScheme("(indent)");
253 | else
254 | {
255 | }
256 |
257 | _needReflow = true;
258 | _needRedraw = true;
259 | break;
260 | }
261 |
262 | case SDL_TEXTINPUT:
263 | {
264 | _timeSinceKeypress = 0;
265 | const(char)[] s = fromStringz(event.text.text.ptr);
266 |
267 | if (s == ":")
268 | _engine.toggleCommandLineMode();
269 | else
270 | {
271 | dstring ds = to!dstring(s);
272 | foreach(ch; ds)
273 | _engine.executeScheme(format("(insert-char %s)", to!int(ch)));
274 | }
275 | }
276 | _needRedraw = true;
277 | break;
278 |
279 |
280 | case SDL_MOUSEBUTTONDOWN:
281 | {
282 | const (SDL_MouseButtonEvent*) mbEvent = &event.button;
283 |
284 | // undo
285 | if (mbEvent.button == SDL_BUTTON_X1)
286 | _engine.executeScheme("(undo)");
287 | else if (mbEvent.button == SDL_BUTTON_X2)
288 | _engine.executeScheme("(redo)");
289 | else
290 | _mainPanel.mouseClick(_sdl2.mouse.x, _sdl2.mouse.y, mbEvent.button, mbEvent.clicks > 1);
291 | _needRedraw = true;
292 | break;
293 | }
294 |
295 | case SDL_MOUSEBUTTONUP:
296 | {
297 | const (SDL_MouseButtonEvent*) mbEvent = &event.button;
298 | _mainPanel.mouseRelease(_sdl2.mouse.x, _sdl2.mouse.y, mbEvent.button);
299 | _needRedraw = true;
300 | break;
301 | }
302 |
303 | case SDL_MOUSEWHEEL:
304 | {
305 | _mainPanel.mouseWheel(_sdl2.mouse.x, _sdl2.mouse.y, _sdl2.mouse.wheelDeltaX(), _sdl2.mouse.wheelDeltaY());
306 | _needRedraw = true;
307 | break;
308 | }
309 |
310 | case SDL_MOUSEMOTION:
311 | _mainPanel.mouseMove(_sdl2.mouse.x, _sdl2.mouse.y, _sdl2.mouse.lastDeltaX(), _sdl2.mouse.lastDeltaY());
312 | _needRedraw = true;
313 | break;
314 |
315 | case SDL_WINDOWEVENT:
316 | /*{
317 | const (SDL_WindowEvent*) windowEvent = &event.window;
318 |
319 | if (windowEvent.event == ) {*/
320 |
321 |
322 | _needReflow = true;
323 | _needRedraw = true;
324 | break;
325 |
326 |
327 | default:
328 | break;
329 | }
330 | }
331 |
332 | void waitForAnEventThenProcessThemAll()
333 | {
334 | SDL_Event event;
335 | if (_sdl2.waitEventTimeout(&event, 50))
336 | {
337 | dealWithEvent(event);
338 | while (_sdl2.pollEvent(&event))
339 | {
340 | dealWithEvent(event);
341 | }
342 | }
343 | }
344 | }
345 |
--------------------------------------------------------------------------------
/source/dido/panel/textarea.d:
--------------------------------------------------------------------------------
1 | module dido.panel.textarea;
2 |
3 | import std.conv : to;
4 | import std.algorithm : min, max;
5 |
6 | import gfm.sdl2;
7 | import gfm.math.vector;
8 |
9 | import dido.buffer.selection;
10 | import dido.gui;
11 | import dido.buffer.buffer;
12 | import dido.buffer.bufferiterator;
13 |
14 | class TextArea : UIElement
15 | {
16 | public:
17 |
18 | this(UIContext context, int marginEditor, bool haveLineNumbers, bool hasScrollbars, vec4ub backgroundColor)
19 | {
20 | super(context);
21 |
22 | _marginEditor = marginEditor;
23 |
24 | if (hasScrollbars)
25 | {
26 | addChild(new VertScrollBar(context, 9, 4, true));
27 | verticalScrollbar = cast(ScrollBar) child(0);
28 |
29 | addChild(new HorzScrollBar(context, 9, 4, false));
30 | horizontalScrollbar = cast(ScrollBar) child(1);
31 | }
32 |
33 | if (haveLineNumbers)
34 | {
35 | lineNumberArea = new LineNumberArea(context);
36 | addChild(lineNumberArea);
37 | }
38 |
39 | _editCursor = new SDL2Cursor(context.sdl2, SDL_SYSTEM_CURSOR_IBEAM);
40 | _previousCursor = SDL2Cursor.getCurrent(context.sdl2);
41 | _backgroundColor = backgroundColor;
42 | }
43 |
44 | override void close()
45 | {
46 | _editCursor.close();
47 | _previousCursor.close();
48 | foreach(ref child; children)
49 | child.close();
50 | }
51 |
52 | override void reflow(box2i availableSpace)
53 | {
54 | if (lineNumberArea !is null)
55 | {
56 | lineNumberArea.reflow(availableSpace);
57 | availableSpace.min.x = lineNumberArea.position.max.x;
58 | }
59 |
60 | if (verticalScrollbar !is null)
61 | {
62 | box2i availableForVert = availableSpace;
63 | verticalScrollbar.reflow(availableForVert);
64 | availableSpace.max.x = verticalScrollbar.position.min.x;
65 | }
66 |
67 | if (horizontalScrollbar !is null)
68 | {
69 | horizontalScrollbar.reflow(availableSpace);
70 | }
71 | _position = availableSpace;
72 | }
73 |
74 | // Returns number of simultaneously visible lines
75 | int numVisibleLines() pure const nothrow
76 | {
77 | int result = (_position.height - 16) / charHeight;
78 | if (result < 1)
79 | result = 1;
80 | return result;
81 | }
82 |
83 | override void preRender(SDL2Renderer renderer)
84 | {
85 | if (_backgroundColor.a != 0)
86 | {
87 | renderer.setColor(_backgroundColor.r, _backgroundColor.g, _backgroundColor.b, _backgroundColor.a);
88 | renderer.fillRect(0, 0, _position.width, _position.height);
89 | }
90 |
91 | int editPosX = -_cameraX + _marginEditor;
92 | int editPosY = -_cameraY + _marginEditor;
93 |
94 | int firstVisibleLine = getFirstVisibleLine();
95 | int firstNonVisibleLine = getFirstNonVisibleLine();
96 |
97 | int firstVisibleColumn = getFirstVisibleColumn();
98 | int firstNonVisibleColumn = getFirstNonVisibleColumn();
99 | int longestLineLength = _buffer.getLongestLineLength();
100 |
101 | box2i visibleBox = box2i(0, 0, _position.width, _position.height);
102 |
103 | for (int i = firstVisibleLine; i < firstNonVisibleLine; ++i)
104 | {
105 | dstring line = _buffer.line(i);
106 |
107 | int posXInChars = 0;
108 | int posY = editPosY + i * charHeight;
109 |
110 | int maxCol = min(line.length, firstNonVisibleColumn);
111 |
112 | // Allows to draw cursor on the very last file position
113 | if ( i + 1 == _buffer.numLines() )
114 | maxCol++;
115 |
116 | for(int j = firstVisibleColumn; j < maxCol; ++j)
117 | {
118 | Buffer.Hit hit = _buffer.intersectsSelection(Cursor(i, j));
119 | bool charIsSelected = hit.charInSelection;
120 |
121 | dchar ch = j < line.length ? line[j] : ' '; // last line read a little to far
122 | int widthOfChar = 1;
123 | bool drawDot = false;
124 | switch (ch)
125 | {
126 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
127 | font.setColor(255, 200, 200);
128 | break;
129 |
130 | case '+', '-', '=', '>', '<', '^', ',', '$', '|', '&', '`', '/', '@', '.', '"', '[', ']', '?', ':', '\'', '\\':
131 | font.setColor(255, 255, 106);
132 | break;
133 |
134 | case '(', ')', ';':
135 | font.setColor(255, 255, 150);
136 | break;
137 |
138 | case '{':
139 | font.setColor(108, 108, 128);
140 | break;
141 |
142 | case '}':
143 | font.setColor(108, 108, 138);
144 | break;
145 |
146 | case '\n':
147 | ch = ' ';
148 | break;
149 |
150 | case '\t':
151 | ch = 0x2192;
152 | font.setColor(80, 60, 70);
153 | int tabLength = 1;//4; not working yet
154 | widthOfChar = tabLength - posXInChars % tabLength;
155 | break;
156 |
157 | case ' ':
158 | if (charIsSelected)
159 | {
160 | drawDot = true;
161 | }
162 | break;
163 |
164 | default:
165 | font.setColor(250, 250, 250);
166 | break;
167 | }
168 |
169 | int posX = editPosX + posXInChars * charWidth;
170 | box2i charBox = box2i(posX, posY, posX + charWidth, posY + charHeight);
171 |
172 | if (visibleBox.intersects(charBox))
173 | {
174 | if (charIsSelected)
175 | {
176 | // draw selection background
177 | renderer.setColor(43, 54, 66, 255);
178 | renderer.fillRect(charBox.min.x, charBox.min.y, charBox.width, charBox.height);
179 | }
180 |
181 | if (drawDot)
182 | {
183 | renderer.setColor(131, 137, 152, 255);
184 | renderer.fillRect(charBox.min.x + charBox.width/2, charBox.min.y + charBox.height/2, 1, 1);
185 | }
186 | else if (ch != ' ')
187 | font.renderChar(ch, posX, posY);
188 |
189 | if (hit.cursorThere && _drawCursors)
190 | {
191 | // draw cursor
192 | renderer.setColor(255, 255, 255, 255);
193 | renderer.fillRect(charBox.min.x, charBox.min.y, 1, charHeight - 1);
194 | }
195 | }
196 | posXInChars += widthOfChar;
197 | }
198 | }
199 |
200 | if (lineNumberArea !is null)
201 | {
202 | lineNumberArea.setState(_buffer, _marginEditor, firstVisibleLine, firstNonVisibleLine, _cameraY);
203 | }
204 |
205 | if (verticalScrollbar !is null)
206 | {
207 | int actualRange = maxCameraY() + _position.height;
208 | float start = _cameraY / cast(float)(actualRange);
209 | float stop = (_cameraY + _position.height) / cast(float)(actualRange);
210 | verticalScrollbar.setProgress(start, stop);
211 |
212 | // annoying grey rect in scrollbar crossing
213 | int scrollSize = verticalScrollbar.position.width;
214 | renderer.setViewport(_position.min.x, _position.min.y, _position.width + scrollSize, _position.height + scrollSize);
215 | renderer.setColor(0x30, 0x2C, 0x2C, 255);
216 | renderer.fillRect(_position.width, _position.height, scrollSize, scrollSize);
217 | }
218 |
219 | if (horizontalScrollbar !is null)
220 | {
221 | int actualRange = maxCameraX() + _position.width;
222 | float start = _cameraX / cast(float)(actualRange);
223 | float stop = (_cameraX + _position.width) / cast(float)(actualRange);
224 | horizontalScrollbar.setProgress(start, stop);
225 | }
226 | }
227 |
228 | void setState(Buffer buffer, bool drawCursors)
229 | {
230 | _buffer = buffer;
231 | _drawCursors = drawCursors;
232 | }
233 |
234 | void clearCamera()
235 | {
236 | _cameraX = 0;
237 | _cameraY = 0;
238 | }
239 |
240 | void moveCamera(int dx, int dy)
241 | {
242 | if (_buffer is null)
243 | return;
244 |
245 | _cameraX += dx;
246 | _cameraY += dy;
247 | normalizeCamera();
248 | }
249 |
250 | override bool onMouseWheel(int x, int y, int wheelDeltaX, int wheelDeltaY)
251 | {
252 | moveCamera(-wheelDeltaX * 3 * charWidth, -wheelDeltaY * 3 * charHeight);
253 | return true;
254 | }
255 |
256 | int maxCameraX()
257 | {
258 | int maxLineLength = _buffer.getLongestLineLength();
259 | return max(0, maxLineLength * charWidth - _position.width);
260 | }
261 |
262 | int maxCameraY()
263 | {
264 | return _buffer.numLines() * charHeight;
265 | }
266 |
267 | void normalizeCamera()
268 | {
269 | if (_cameraX < 0)
270 | _cameraX = 0;
271 |
272 | if (_cameraX > maxCameraX())
273 | _cameraX = maxCameraX();
274 |
275 | if (_cameraY < 0)
276 | _cameraY = 0;
277 |
278 | if (_cameraY > maxCameraY())
279 | _cameraY = maxCameraY();
280 | }
281 |
282 | box2i cameraBox() pure const nothrow
283 | {
284 | return box2i(_cameraX, _cameraY, _cameraX + _position.width, _cameraY + _position.height);
285 | }
286 |
287 | box2i edgeBox(Selection sel)
288 | {
289 | return box2i(_cameraX, _cameraY, _cameraX + _position.width, _cameraY + _position.height);
290 | }
291 |
292 | void ensureOneVisibleSelection()
293 | {
294 | double minDistance = double.infinity;
295 | Selection bestSel;
296 | SelectionSet selset = _buffer.selectionSet();
297 | foreach(Selection sel; selset.selections)
298 | {
299 | double distance = selectionDistance(sel);
300 | if (distance < minDistance)
301 | {
302 | bestSel = sel;
303 | minDistance = distance;
304 | }
305 | }
306 | ensureSelectionVisible(bestSel);
307 | }
308 |
309 | override void onMouseEnter()
310 | {
311 | _editCursor.setCurrent();
312 | }
313 |
314 | override void onMouseExit()
315 | {
316 | _previousCursor.setCurrent();
317 | }
318 |
319 | override bool onMousePostClick(int x, int y, int button, bool isDoubleClick)
320 | {
321 | if (_buffer is null)
322 | return false;
323 |
324 | // implement click on buffer and CTRL + click
325 | if (button == SDL_BUTTON_LEFT || button == SDL_BUTTON_RIGHT)
326 | {
327 | bool ctrl = context.sdl2.keyboard.isPressed(SDLK_LCTRL) || context.sdl2.keyboard.isPressed(SDLK_RCTRL);
328 |
329 | int line = (y - _marginEditor + _cameraY) / charHeight;
330 | int column = (x - _marginEditor + _cameraX + (charWidth / 2)) / charWidth;
331 |
332 | _buffer.addNewSelection(line, column, ctrl);
333 | return true;
334 | }
335 |
336 | return false;
337 | }
338 |
339 | override void onMouseDrag(int x, int y, int dx, int dy)
340 | {
341 | if (_buffer is null)
342 | return;
343 |
344 | // implement drag selection
345 | {
346 | // TODO, need selection timestamps, or some way to have a selection outside of Buffer (better)
347 |
348 | }
349 | }
350 |
351 | class VertScrollBar : ScrollBar
352 | {
353 | public:
354 | this(UIContext context, int widthOfFocusBar, int padding, bool vertical)
355 | {
356 | super(context, widthOfFocusBar, padding, vertical);
357 | }
358 |
359 | override void onScrollChangeMouse(float newProgressStart)
360 | {
361 | int actualRange = maxCameraY() + _position.height;
362 | _cameraY = cast(int)(0.5f + newProgressStart * actualRange);
363 | normalizeCamera();
364 | }
365 | }
366 |
367 | class HorzScrollBar : ScrollBar
368 | {
369 | public:
370 | this(UIContext context, int widthOfFocusBar, int padding, bool vertical)
371 | {
372 | super(context, widthOfFocusBar, padding, vertical);
373 | }
374 |
375 | override void onScrollChangeMouse(float newProgressStart)
376 | {
377 | int actualRange = maxCameraX() + _position.width;
378 | _cameraX = cast(int)(0.5f + newProgressStart * actualRange);
379 | normalizeCamera();
380 | }
381 | }
382 |
383 | private:
384 | int _cameraX = 0;
385 | int _cameraY = 0;
386 | Buffer _buffer;
387 | bool _drawCursors;
388 |
389 | vec4ub _backgroundColor;
390 |
391 | SDL2Cursor _editCursor;
392 | SDL2Cursor _previousCursor;
393 |
394 | int _marginEditor;
395 |
396 | LineNumberArea lineNumberArea;
397 | ScrollBar verticalScrollbar;
398 | ScrollBar horizontalScrollbar;
399 |
400 | int getFirstVisibleLine() pure const nothrow
401 | {
402 | return max(0, _cameraY / charHeight - 1);
403 | }
404 |
405 | int getFirstVisibleColumn() pure const nothrow
406 | {
407 | return max(0, _cameraX / charWidth - 1);
408 | }
409 |
410 | int getFirstNonVisibleLine() pure const nothrow
411 | {
412 | return min(_buffer.numLines(), 1 + (_cameraY + _position.height + charHeight - 1) / charHeight);
413 | }
414 |
415 | int getFirstNonVisibleColumn() pure const nothrow
416 | {
417 | return min(_buffer.getLongestLineLength(), 1 + (_cameraX + _position.width + charWidth - 1) / charWidth);
418 | }
419 |
420 | box2i getEdgeBox(Selection selection)
421 | {
422 | vec2i edgePos = vec2i(selection.edge.cursor.column * charWidth + _marginEditor,
423 | selection.edge.cursor.line * charHeight + _marginEditor);
424 | return box2i(edgePos.x, edgePos.y, edgePos.x + charWidth, edgePos.y + charHeight);
425 | }
426 |
427 | // 0 if visible
428 | // more if not visible
429 | double selectionDistance(Selection selection)
430 | {
431 | return cameraBox().distance(getEdgeBox(selection));
432 | }
433 |
434 | void ensureSelectionVisible(Selection selection)
435 | {
436 | int scrollMargin = _marginEditor;
437 | box2i edgeBox = getEdgeBox(selection);
438 | box2i camBox = cameraBox();
439 | if (edgeBox.min.x < camBox.min.x + scrollMargin)
440 | _cameraX += (edgeBox.min.x - camBox.min.x - scrollMargin);
441 |
442 | if (edgeBox.max.x > camBox.max.x - scrollMargin)
443 | _cameraX += (edgeBox.max.x - camBox.max.x + scrollMargin);
444 |
445 | if (edgeBox.min.y < camBox.min.y + scrollMargin)
446 | _cameraY += (edgeBox.min.y - camBox.min.y - scrollMargin);
447 | if (edgeBox.max.y > camBox.max.y - scrollMargin)
448 | _cameraY += (edgeBox.max.y - camBox.max.y + scrollMargin);
449 | normalizeCamera();
450 | }
451 |
452 | void renderSelectionForeground(SDL2Renderer renderer, int offsetX, int offsetY, Selection selection, bool drawCursors)
453 | {
454 | Selection sorted = selection.sorted();
455 |
456 | // don't draw invisible selections
457 | if (sorted.edge.cursor.line < getFirstVisibleLine())
458 | return;
459 |
460 | if (sorted.anchor.cursor.line >= getFirstNonVisibleLine())
461 | return;
462 |
463 |
464 | if (drawCursors)
465 | {
466 | int startX = offsetX + selection.edge.cursor.column * charWidth;
467 | int startY = offsetY + selection.edge.cursor.line * charHeight;
468 |
469 | renderer.setColor(255, 255, 255, 255);
470 | renderer.fillRect(startX, startY, 1, charHeight - 1);
471 | }
472 | }
473 | }
474 |
475 |
476 | class LineNumberArea : UIElement
477 | {
478 | public:
479 |
480 | this(UIContext context)
481 | {
482 | super(context);
483 | }
484 |
485 | override void reflow(box2i availableSpace)
486 | {
487 | _position = availableSpace;
488 | _position.max.x = _position.min.x + 6 * charWidth;
489 | }
490 |
491 | override void preRender(SDL2Renderer renderer)
492 | {
493 | renderer.setColor(28, 28, 28, 255);
494 | renderer.fillRect(0, 0, _position.width, _position.height);
495 |
496 | for (int i = _firstVisibleLine; i < _firstNonVisibleLine; ++i)
497 | {
498 | dstring lineNumber = to!dstring(i + 1) ~ " ";
499 | while (lineNumber.length < 6)
500 | {
501 | lineNumber = " "d ~ lineNumber;
502 | }
503 |
504 | font.setColor(49, 97, 107, 160);
505 | font.renderString(lineNumber, 0, 0 -_cameraY + _marginEditor + i * charHeight);
506 | }
507 | }
508 |
509 | void setState(Buffer buffer, int marginEditor, int firstVisibleLine, int firstNonVisibleLine, int cameraY)
510 | {
511 | _buffer = buffer;
512 | _cameraY = cameraY;
513 | _firstVisibleLine = firstVisibleLine;
514 | _firstNonVisibleLine = firstNonVisibleLine;
515 | _marginEditor = marginEditor;
516 | }
517 |
518 | private:
519 | Buffer _buffer;
520 | int _cameraY;
521 | int _marginEditor;
522 | int _firstVisibleLine;
523 | int _firstNonVisibleLine;
524 | }
525 |
526 |
527 |
--------------------------------------------------------------------------------
/source/dido/engine.d:
--------------------------------------------------------------------------------
1 | module dido.engine;
2 |
3 | import std.process;
4 | import std.conv;
5 | import std.string;
6 | import std.concurrency;
7 |
8 | import gfm.sdl2;
9 |
10 | import dido.panel;
11 | import dido.buffer.buffer;
12 | import dido.window;
13 | import dido.builder;
14 | import dido.project;
15 |
16 | import schemed;
17 |
18 | // Model
19 | class DidoEngine
20 | {
21 | private:
22 | TextArea _textArea;
23 | CommandLinePanel _cmdlinePanel;
24 | OutputPanel _outputPanel;
25 | bool _commandLineMode;
26 | Buffer _bufferEdit; // current buffer in edit area
27 | Project[] _projects;
28 |
29 | int _projectSelect;
30 |
31 | Window _window;
32 | SDL2 _sdl2;
33 | bool _finished;
34 | Builder _builder;
35 |
36 | // scheme-d environment
37 | Environment _env;
38 |
39 | public:
40 |
41 | this(SDL2 sdl2, Window window, TextArea textArea, CommandLinePanel cmdlinePanel, OutputPanel outputPanel, string[] dubPaths)
42 | {
43 | foreach (ref dubPath; dubPaths)
44 | {
45 | Project project = new Project(dubPath);
46 | _projects ~= project;
47 | }
48 |
49 | setCurrentProject(0);
50 | currentProject.setCurrentBuffer(0);
51 | showCurrentBuffer();
52 |
53 | _textArea = textArea;
54 | _outputPanel = outputPanel;
55 | _cmdlinePanel = cmdlinePanel;
56 | _window = window;
57 | _commandLineMode = false;
58 | _finished = false;
59 | _sdl2 = sdl2;
60 |
61 | _env = defaultEnvironment();
62 | addBuiltins(_env);
63 |
64 | _builder = new Builder(this);
65 | }
66 |
67 | void showCurrentBuffer()
68 | {
69 | setEditableBuffer(currentProject.currentBuffer);
70 | }
71 |
72 | Project[] projects()
73 | {
74 | return _projects;
75 | }
76 |
77 | bool isCommandLineMode()
78 | {
79 | return _commandLineMode;
80 | }
81 |
82 | // change the current editable
83 | void setEditableBuffer(Buffer buffer)
84 | {
85 | _bufferEdit = buffer;
86 | _bufferEdit.ensureLoaded();
87 | }
88 |
89 | // change current edited buffer
90 | void setCurrentProject(int projectSelect)
91 | {
92 | _projectSelect = projectSelect;
93 | }
94 |
95 | int projectSelect()
96 | {
97 | return _projectSelect;
98 | }
99 |
100 | Project currentProject()
101 | {
102 | return _projects[_projectSelect];
103 | }
104 |
105 | Buffer currentEditBuffer()
106 | {
107 | return _bufferEdit;
108 | }
109 |
110 | bool finished()
111 | {
112 | return _finished;
113 | }
114 |
115 | Buffer currentBuffer()
116 | {
117 | if (_commandLineMode)
118 | return _cmdlinePanel.buffer();
119 | else
120 | return _bufferEdit;
121 | }
122 |
123 | TextArea currentTextArea()
124 | {
125 | if (_commandLineMode)
126 | return _cmdlinePanel.textArea();
127 | else
128 | return _textArea;
129 | }
130 |
131 | void enterCommandLineMode()
132 | {
133 | if (!_commandLineMode)
134 | {
135 | _commandLineMode = true;
136 | currentBuffer().clearContent();
137 | }
138 | }
139 |
140 | void leaveCommandLineMode()
141 | {
142 | if (_commandLineMode)
143 | {
144 | _commandLineMode = false;
145 | currentTextArea().ensureOneVisibleSelection();
146 | }
147 | }
148 |
149 | void toggleCommandLineMode()
150 | {
151 | if (!_commandLineMode)
152 | enterCommandLineMode();
153 | else
154 | leaveCommandLineMode();
155 | }
156 |
157 | void logMessage(LineType type, dstring msg)
158 | {
159 | _outputPanel.log(LineOutput(type, msg));
160 | }
161 |
162 | void greenMessage(dstring msg)
163 | {
164 | _outputPanel.log(LineOutput(LineType.SUCCESS, msg));
165 | }
166 |
167 | void redMessage(dstring msg)
168 | {
169 | _outputPanel.log(LineOutput(LineType.ERROR, msg));
170 | }
171 |
172 | void executeScheme(string code, bool echo = false)
173 | {
174 | if (echo)
175 | _outputPanel.log(LineOutput(LineType.COMMAND, ":"d ~ to!dstring(code)));
176 |
177 | Atom result;
178 | try
179 | {
180 | result = Atom(execute(code, _env)); // result is discarded
181 | }
182 | catch(SchemeParseException e)
183 | {
184 | // try to execute again, but with parens appended
185 | try
186 | {
187 | result = Atom(execute("(" ~ code ~ ")", _env));
188 | }
189 | catch(SchemeParseException e2)
190 | {
191 | // another error, print the _first_ message
192 | redMessage(to!dstring(e.msg));
193 | return;
194 | }
195 | catch(SchemeEvalException e2)
196 | {
197 | redMessage(to!dstring(e2.msg));
198 | return;
199 | }
200 | }
201 | catch(SchemeEvalException e)
202 | {
203 | redMessage(to!dstring(e.msg));
204 | return;
205 | }
206 |
207 | // output result
208 | if (echo)
209 | _outputPanel.log(LineOutput(LineType.RESULT, to!dstring("=> " ~ result.toString)));
210 | }
211 |
212 | void executeCommandLine(dstring cmdline)
213 | {
214 | if (cmdline == ""d)
215 | _bufferEdit.insertChar(':');
216 | else
217 | {
218 | executeScheme(to!string(cmdline), true);
219 | }
220 | }
221 |
222 | void enter()
223 | {
224 | // TODO implement this logic in Scheme itself
225 | if (_commandLineMode)
226 | {
227 | executeCommandLine(_cmdlinePanel.getCommandLine());
228 | _commandLineMode = false;
229 | }
230 | else
231 | {
232 | currentBuffer.insertChar('\n');
233 | currentTextArea.ensureOneVisibleSelection();
234 | }
235 | }
236 |
237 | bool checkArgs(string func, Atom[] args, int min, int max)
238 | {
239 | if (args.length < min)
240 | {
241 | redMessage(to!dstring(format("%s expects %s to %s arguments", to!string(func), min, max)));
242 | return false;
243 | }
244 | else if (args.length > max)
245 | {
246 | redMessage(to!dstring(format("%s expects %s to %s arguments", to!string(func), min, max)));
247 | return false;
248 | }
249 | else
250 | return true;
251 | }
252 |
253 | void addBuiltins(Environment env)
254 | {
255 | env.addBuiltin("display", (Atom[] args)
256 | {
257 | foreach(arg; args)
258 | _outputPanel.log(LineOutput(LineType.RESULT, to!dstring(arg.toString)));
259 | return makeNil();
260 | });
261 |
262 | env.addBuiltin("exit", (Atom[] args)
263 | {
264 | if (!checkArgs("q|exit", args, 0, 0))
265 | return makeNil();
266 | _finished = true;
267 | greenMessage("Bye"d);
268 | return makeNil();
269 | });
270 |
271 | env.addBuiltin("undo", (Atom[] args)
272 | {
273 | if (!checkArgs("u|undo", args, 0, 0))
274 | return makeNil();
275 | currentBuffer().undo();
276 | currentTextArea().ensureOneVisibleSelection();
277 | return makeNil();
278 | });
279 |
280 | env.addBuiltin("redo", (Atom[] args)
281 | {
282 | if (!checkArgs("r|redo", args, 0, 0))
283 | return makeNil();
284 | currentBuffer().redo();
285 | currentTextArea().ensureOneVisibleSelection();
286 | return makeNil();
287 | });
288 |
289 | env.addBuiltin("new", (Atom[] args)
290 | {
291 | if (!checkArgs("n|new", args, 0, 0))
292 | return makeNil();
293 | currentProject().buffers() ~= new Buffer;
294 | currentProject().setCurrentBuffer(currentProject().numBuffers() - 1);
295 | showCurrentBuffer();
296 | greenMessage("Created new file"d);
297 | return makeNil();
298 | });
299 |
300 | env.addBuiltin("clean", (Atom[] args)
301 | {
302 | if (!checkArgs("clean", args, 0, 0))
303 | return makeNil();
304 | _bufferEdit.cleanup();
305 | greenMessage("Buffer cleaned up"d);
306 | return makeNil();
307 | });
308 |
309 | env.addBuiltin("save", (Atom[] args)
310 | {
311 | if (!checkArgs("s|save", args, 0, 0))
312 | return makeNil();
313 |
314 | if (_bufferEdit.isBoundToFileName())
315 | {
316 | string filepath = _bufferEdit.filePath();
317 | _bufferEdit.saveToFile(filepath);
318 | greenMessage(to!dstring(format("Saved to %s", filepath)));
319 | }
320 | else
321 | {
322 | redMessage("This buffer is unbounded, try :save ");
323 | }
324 | return makeNil();
325 | });
326 |
327 | env.addBuiltin("load", (Atom[] args)
328 | {
329 | if (!checkArgs("l|load", args, 0, 0))
330 | return makeNil();
331 |
332 | if (_bufferEdit.isBoundToFileName())
333 | {
334 | string filepath = _bufferEdit.filePath();
335 | _bufferEdit.loadFromFile(filepath);
336 | greenMessage(to!dstring(format("Loaded %s", filepath)));
337 | }
338 | else
339 | redMessage("This buffer is unbounded, try :load ");
340 | return makeNil();
341 | });
342 |
343 | env.addBuiltin("stop-build", (Atom[] args)
344 | {
345 | if (!checkArgs("stop-build", args, 0, 0))
346 | return makeNil();
347 | _builder.stopBuild();
348 | return makeNil();
349 | });
350 |
351 | env.addBuiltin("build", (Atom[] args)
352 | {
353 | if (!checkArgs("build", args, 3, 3))
354 | return makeNil();
355 |
356 | string compiler = args[0].toString();
357 | string arch = args[1].toString();
358 | string build = args[2].toString();
359 | _builder.startBuild(compiler, arch, build);
360 | return makeNil();
361 | });
362 |
363 | env.addBuiltin("run", (Atom[] args)
364 | {
365 | if (!checkArgs("run", args, 3, 3))
366 | return makeNil();
367 |
368 | string compiler = args[0].toString();
369 | string arch = args[1].toString();
370 | string build = args[2].toString();
371 | _builder.startRun(compiler, arch, build);
372 | return makeNil();
373 | });
374 |
375 | env.addBuiltin("test", (Atom[] args)
376 | {
377 | if (!checkArgs("test", args, 3, 3))
378 | return makeNil();
379 |
380 | string compiler = args[0].toString();
381 | string arch = args[1].toString();
382 | string build = args[2].toString();
383 | _builder.startTest(compiler, arch, build);
384 | return makeNil();
385 | });
386 |
387 | env.addBuiltin("copy", (Atom[] args)
388 | {
389 | if (!checkArgs("copy", args, 0, 0))
390 | return makeNil();
391 | _sdl2.setClipboard(to!string(currentBuffer.copy()));
392 | currentTextArea.ensureOneVisibleSelection();
393 | return makeNil();
394 | });
395 |
396 | env.addBuiltin("cut", (Atom[] args)
397 | {
398 | if (!checkArgs("cut", args, 0, 0))
399 | return makeNil();
400 | _sdl2.setClipboard(to!string(currentBuffer.cut()));
401 | currentTextArea.ensureOneVisibleSelection();
402 | return makeNil();
403 | });
404 |
405 | env.addBuiltin("paste", (Atom[] args)
406 | {
407 | if (!checkArgs("paste", args, 0, 0))
408 | return makeNil();
409 | dstring clipboard = to!dstring(_sdl2.getClipboard());
410 | currentBuffer.paste(clipboard);
411 | currentTextArea.ensureOneVisibleSelection();
412 | return makeNil();
413 | });
414 |
415 |
416 | // aliases
417 | env.values["n"] = env.values["new"];
418 | env.values["s"] = env.values["save"];
419 | env.values["l"] = env.values["load"];
420 | env.values["u"] = env.values["undo"];
421 | env.values["r"] = env.values["redo"];
422 | env.values["q"] = env.values["exit"];
423 |
424 | env.addBuiltin("visible-lines", (Atom[] args)
425 | {
426 | if (!checkArgs("visible-lines", args, 0, 0))
427 | return makeNil();
428 | double lines = _textArea.numVisibleLines;
429 | return Atom(lines);
430 | });
431 |
432 | env.addBuiltin("move-vertical", (Atom[] args)
433 | {
434 | if (!checkArgs("move-vertical", args, 2, 2))
435 | return makeNil();
436 | int displacement = to!int(args[0].toDouble);
437 | bool shift = args[1].toBool;
438 | currentBuffer.moveSelectionVertical(displacement, shift);
439 | currentTextArea.ensureOneVisibleSelection();
440 | return makeNil();
441 | });
442 |
443 | env.addBuiltin("move-horizontal", (Atom[] args)
444 | {
445 | if (!checkArgs("move-horizontal", args, 2, 2))
446 | return makeNil();
447 | int displacement = to!int(args[0].toDouble);
448 | bool shift = args[1].toBool;
449 | currentBuffer.moveSelectionHorizontal(displacement, shift);
450 | currentTextArea.ensureOneVisibleSelection();
451 | return makeNil();
452 | });
453 |
454 | env.addBuiltin("move-buffer-start", (Atom[] args)
455 | {
456 | if (!checkArgs("move-buffer-start", args, 1, 1))
457 | return makeNil();
458 | bool shift = args[0].toBool;
459 | currentBuffer.moveSelectionToBufferStart(shift);
460 | currentTextArea.ensureOneVisibleSelection();
461 | return makeNil();
462 | });
463 |
464 | env.addBuiltin("move-buffer-end", (Atom[] args)
465 | {
466 | if (!checkArgs("move-buffer-end", args, 1, 1))
467 | return makeNil();
468 | bool shift = args[0].toBool;
469 | currentBuffer.moveSelectionToBufferEnd(shift);
470 | currentTextArea.ensureOneVisibleSelection();
471 | return makeNil();
472 | });
473 |
474 | env.addBuiltin("move-word-left", (Atom[] args)
475 | {
476 | if (!checkArgs("move-word-left", args, 1, 1))
477 | return makeNil();
478 | bool shift = args[0].toBool;
479 | currentBuffer.moveSelectionWord(-1, shift);
480 | currentTextArea.ensureOneVisibleSelection();
481 | return makeNil();
482 | });
483 |
484 | env.addBuiltin("move-word-right", (Atom[] args)
485 | {
486 | if (!checkArgs("move-word-right", args, 1, 1))
487 | return makeNil();
488 | bool shift = args[0].toBool;
489 | currentBuffer.moveSelectionWord(1, shift);
490 | currentTextArea.ensureOneVisibleSelection();
491 | return makeNil();
492 | });
493 |
494 | env.addBuiltin("move-line-start", (Atom[] args)
495 | {
496 | if (!checkArgs("move-line-start", args, 1, 1))
497 | return makeNil();
498 | bool shift = args[0].toBool;
499 | currentBuffer.moveToLineBegin(shift);
500 | currentTextArea.ensureOneVisibleSelection();
501 | return makeNil();
502 | });
503 |
504 | env.addBuiltin("move-line-end", (Atom[] args)
505 | {
506 | if (!checkArgs("move-line-end", args, 1, 1))
507 | return makeNil();
508 | bool shift = args[0].toBool;
509 | currentBuffer.moveToLineEnd(shift);
510 | currentTextArea.ensureOneVisibleSelection();
511 | return makeNil();
512 | });
513 |
514 | env.addBuiltin("extend-selection-vertical", (Atom[] args)
515 | {
516 | if (!checkArgs("extend-selection-vertical", args, 1, 1))
517 | return makeNil();
518 | int displacement = to!int(args[0].toDouble);
519 | currentBuffer.extendSelectionVertical(displacement);
520 | currentTextArea.ensureOneVisibleSelection();
521 | return makeNil();
522 | });
523 |
524 | env.addBuiltin("select-all", (Atom[] args)
525 | {
526 | if (!checkArgs("select-all", args, 0, 0))
527 | return makeNil();
528 | currentBuffer.selectAll();
529 | currentTextArea.ensureOneVisibleSelection();
530 | return makeNil();
531 | });
532 |
533 | env.addBuiltin("next-project", (Atom[] args)
534 | {
535 | if (!checkArgs("next-project", args, 0, 0))
536 | return makeNil();
537 | setCurrentProject((_projectSelect + 1) % cast(int) _projects.length );
538 | showCurrentBuffer();
539 | currentTextArea().clearCamera();
540 | currentTextArea().ensureOneVisibleSelection();
541 | return makeNil();
542 | });
543 |
544 | env.addBuiltin("previous-project", (Atom[] args)
545 | {
546 | if (!checkArgs("previous-project", args, 0, 0))
547 | return makeNil();
548 | setCurrentProject((_projectSelect + cast(int) _projects.length - 1) % cast(int) _projects.length );
549 | showCurrentBuffer();
550 | currentTextArea().clearCamera();
551 | currentTextArea().ensureOneVisibleSelection();
552 | return makeNil();
553 | });
554 |
555 | env.addBuiltin("next-buffer", (Atom[] args)
556 | {
557 | if (!checkArgs("next-buffer", args, 0, 0))
558 | return makeNil();
559 | currentProject.nextBuffer();
560 | showCurrentBuffer();
561 | currentTextArea().clearCamera();
562 | currentTextArea().ensureOneVisibleSelection();
563 | return makeNil();
564 | });
565 |
566 | env.addBuiltin("previous-buffer", (Atom[] args)
567 | {
568 | if (!checkArgs("previous-buffer", args, 0, 0))
569 | return makeNil();
570 | currentProject.previousBuffer();
571 | showCurrentBuffer();
572 | currentTextArea().clearCamera();
573 | currentTextArea().ensureOneVisibleSelection();
574 | return makeNil();
575 | });
576 |
577 | env.addBuiltin("toggle-fullscreen", (Atom[] args)
578 | {
579 | if (!checkArgs("toggle-fullscreen", args, 0, 0))
580 | return makeNil();
581 | _window.toggleFullscreen();
582 | currentTextArea.ensureOneVisibleSelection();
583 | return makeNil();
584 | });
585 |
586 | env.addBuiltin("escape", (Atom[] args)
587 | {
588 | if (!checkArgs("escape", args, 0, 0))
589 | return makeNil();
590 | if (_commandLineMode)
591 | _commandLineMode = false;
592 | else
593 | {
594 | currentBuffer.selectionSet().keepOnlyFirst();
595 | currentTextArea.ensureOneVisibleSelection();
596 | }
597 | return makeNil();
598 | });
599 |
600 | env.addBuiltin("indent", (Atom[] args)
601 | {
602 | if (!checkArgs("indent", args, 0, 0))
603 | return makeNil();
604 | currentBuffer.insertTab();
605 | currentTextArea.ensureOneVisibleSelection();
606 | return makeNil();
607 | });
608 |
609 | env.addBuiltin("delete-selection", (Atom[] args)
610 | {
611 | if (!checkArgs("delete", args, 1, 1))
612 | return makeNil();
613 | bool isBackspace = args[0].toBool;
614 | currentBuffer.deleteSelection(isBackspace);
615 | currentTextArea.ensureOneVisibleSelection();
616 | return makeNil();
617 | });
618 |
619 | env.addBuiltin("insert-char", (Atom[] args)
620 | {
621 | if (!checkArgs("insert-char", args, 1, 1))
622 | return makeNil();
623 | dchar ch = to!dchar(to!int(args[0].toDouble));
624 | currentBuffer.insertChar(ch);
625 | currentTextArea.ensureOneVisibleSelection();
626 | return makeNil();
627 | });
628 | }
629 | }
630 |
--------------------------------------------------------------------------------
/source/dido/buffer/buffer.d:
--------------------------------------------------------------------------------
1 | module dido.buffer.buffer;
2 |
3 | import std.file;
4 | import std.string;
5 | import std.array;
6 | import std.conv;
7 | import std.algorithm;
8 |
9 | import dido.buffer.selection;
10 | import dido.buffer.buffercommand;
11 | import dido.buffer.bufferiterator;
12 |
13 |
14 | // text buffers
15 |
16 | final class Buffer
17 | {
18 | private:
19 | string _filepath;
20 | SelectionSet _selectionSet;
21 | dstring[] lines;
22 | int _historyIndex; // where we are in history
23 | BufferCommand[] _history;
24 | bool _hasBeenLoaded;
25 | int _longestLine;
26 |
27 | public:
28 |
29 | // create new
30 | this()
31 | {
32 | lines = ["\n"d];
33 | _selectionSet = new SelectionSet(this);
34 | _filepath = null;
35 | _hasBeenLoaded = true;
36 | updateLongestLine();
37 | }
38 |
39 | this(string filepath)
40 | {
41 | lines = ["\n"d];
42 | _selectionSet = new SelectionSet(this);
43 | _filepath = filepath;
44 | _hasBeenLoaded = false;
45 | updateLongestLine();
46 | }
47 |
48 | void clearContent()
49 | {
50 | lines = ["\n"d];
51 | _selectionSet = new SelectionSet(this);
52 | _filepath = null;
53 | _hasBeenLoaded = true;
54 | updateLongestLine();
55 | }
56 |
57 |
58 | void ensureLoaded()
59 | {
60 | if (!_hasBeenLoaded)
61 | {
62 | assert(_filepath !is null);
63 | loadFromFile(_filepath);
64 | _hasBeenLoaded = true;
65 | }
66 | }
67 |
68 | // load file in buffer, non-conforming utf-8 is lost
69 | void loadFromFile(string path)
70 | {
71 | lines = readTextFile(path, _longestLine);
72 | }
73 |
74 | ubyte[] toSource()
75 | {
76 | ubyte[] result;
77 | for (size_t i = 0; i < lines.length; ++i)
78 | {
79 | string line = to!string(lines[i]);
80 |
81 | // don't output virtual EOL of last line
82 | assert(line.length > 0);
83 | if (i + 1 == lines.length)
84 | line = line[0..$-1];
85 |
86 | version(Windows)
87 | {
88 | if (line.length > 0 && line[$-1] == '\n')
89 | line = line[0..$-1] ~ "\r\n";
90 | result ~= line;
91 | }
92 | }
93 | return result;
94 | }
95 |
96 | dstring getContent()
97 | {
98 | return getSelectionContent(Selection(begin(), end()));
99 | }
100 |
101 | // save file using OS end-of-lines
102 | void saveToFile(string path)
103 | {
104 | std.file.write(path, toSource());
105 | }
106 |
107 | int numLines() pure const nothrow
108 | {
109 | return cast(int)(lines.length);
110 | }
111 |
112 | int lineLength(int lineIndex) pure const nothrow
113 | {
114 | return cast (int) lines[lineIndex].length;
115 | }
116 |
117 | int getLongestLineLength() pure const nothrow
118 | {
119 | return lineLength(_longestLine);
120 | }
121 |
122 | // maximum column allowed for cursor on this line
123 | int maxColumn(int lineIndex) pure const nothrow
124 | {
125 | return cast(int) lines[lineIndex].length - 1;
126 | }
127 |
128 | inout(dstring) line(int lineIndex) pure inout nothrow
129 | {
130 | return lines[lineIndex];
131 | }
132 |
133 | dstring cut()
134 | {
135 | dstring result = copy();
136 | deleteSelection(false);
137 | return result;
138 | }
139 |
140 | dstring copy()
141 | {
142 | dstring result = ""d;
143 | int sellen = cast(int)_selectionSet.selections.length;
144 | for (int i = 0; i < sellen; ++i)
145 | {
146 | if (i > 0)
147 | result ~= '\n';
148 | result ~= getSelectionContent(_selectionSet.selections[i]);
149 | }
150 | return result;
151 | }
152 |
153 | void paste(dstring clipboardContent)
154 | {
155 | // removes \r from clipboard string (system might add it for interop reasons)
156 | dstring clearedClipBoard;
157 | int l = 0;
158 | for (int i = 0; i < cast(int)(clipboardContent.length); ++i)
159 | if (clipboardContent[i] != '\r')
160 | clearedClipBoard ~= clipboardContent[i];
161 |
162 | if (_selectionSet.selections.length > 1)
163 | {
164 | dstring[] byLineContent = splitLines(clearedClipBoard);
165 | bySelectionEdit( (int i)
166 | {
167 | if (i < byLineContent.length)
168 | return byLineContent[i];
169 | else
170 | return ""d;
171 | } );
172 | }
173 | else
174 | {
175 | bySelectionEdit( (int i)
176 | {
177 | return clearedClipBoard;
178 | } );
179 | }
180 | }
181 |
182 | void undo()
183 | {
184 | while (_historyIndex > 0)
185 | {
186 | _historyIndex--;
187 | if (reverseCommand(_history[_historyIndex]))
188 | break;
189 | }
190 | }
191 |
192 | void redo()
193 | {
194 | int barrierFound = 0;
195 | while (_historyIndex < _history.length)
196 | {
197 | if (_history[_historyIndex].type == BufferCommandType.BARRIER)
198 | barrierFound++;
199 |
200 | if (barrierFound >= 2)
201 | return;
202 |
203 | applyCommand(_history[_historyIndex]);
204 | _historyIndex++;
205 | }
206 | }
207 |
208 | bool isBoundToFileName()
209 | {
210 | return _filepath !is null;
211 | }
212 |
213 | void moveSelectionHorizontal(int dx, bool shift)
214 | {
215 | foreach(ref sel; _selectionSet.selections)
216 | {
217 | sel.edge = sel.edge + dx;
218 |
219 | if (!shift)
220 | sel.anchor = sel.edge;
221 | }
222 | _selectionSet.normalize();
223 | }
224 |
225 | void moveSelectionVertical(int dy, bool shift)
226 | {
227 | foreach(ref sel; _selectionSet.selections)
228 | {
229 | sel.edge.cursor = clampCursor(Cursor(sel.edge.cursor.line + dy, sel.edge.cursor.column));
230 |
231 | if (!shift)
232 | sel.anchor = sel.edge;
233 | }
234 | _selectionSet.normalize();
235 | }
236 |
237 | // move by words
238 | void moveSelectionWord(int dx, bool shift)
239 | {
240 | assert(dx == -1 || dx == 1);
241 | foreach(ref sel; _selectionSet.selections)
242 | {
243 | BufferIterator edge = sel.edge;
244 |
245 | if (dx == -1 && edge.canBeDecremented)
246 | --edge;
247 |
248 | static bool isSpace(dchar ch)
249 | {
250 | static immutable dstring spaces = " \n\t\r"d;
251 | for (size_t i = 0; i < spaces.length; ++i)
252 | if (ch == spaces[i])
253 | return true;
254 | return false;
255 | }
256 |
257 | static bool isOperator(dchar ch)
258 | {
259 | static immutable dstring operators = "+-=><^,$|&`/@.\"[](){}?:\'\\"d;
260 | for (size_t i = 0; i < operators.length; ++i)
261 | if (ch == operators[i])
262 | return true;
263 | return false;
264 | }
265 |
266 | static bool isAlnum(dchar ch)
267 | {
268 | return !(isSpace(ch) || isOperator(ch));
269 | }
270 |
271 | bool wentThroughLineBreak = false;
272 |
273 | while(true)
274 | {
275 | dchar ch = edge.read();
276 | if (ch == '\n')
277 | wentThroughLineBreak = true;
278 | if (! (isSpace(ch) && edge.canGoInDirection(dx)) )
279 | break;
280 | edge += dx;
281 | }
282 |
283 | if (!wentThroughLineBreak)
284 | {
285 | bool isOp = isOperator(edge.read);
286 | if (isOp)
287 | {
288 | while( isOperator(edge.read) && edge.canGoInDirection(dx) )
289 | edge += dx;
290 | }
291 | else
292 | {
293 | while( isAlnum(edge.read) && edge.canGoInDirection(dx) )
294 | edge += dx;
295 | }
296 | }
297 |
298 | if (dx == -1 && edge.canBeIncremented)
299 | ++edge;
300 |
301 | sel.edge = edge;
302 | if (!shift)
303 | sel.anchor = sel.edge;
304 | _selectionSet.normalize();
305 | }
306 | }
307 |
308 |
309 | void moveSelectionToBufferStart(bool shift)
310 | {
311 | foreach(ref sel; _selectionSet.selections)
312 | {
313 | sel.edge = begin();
314 | if (!shift)
315 | sel.anchor = sel.edge;
316 | }
317 | _selectionSet.normalize();
318 | }
319 |
320 | void moveSelectionToBufferEnd(bool shift)
321 | {
322 | foreach(ref sel; _selectionSet.selections)
323 | {
324 | sel.edge = end();
325 | if (!shift)
326 | sel.anchor = sel.edge;
327 | }
328 | _selectionSet.normalize();
329 | }
330 |
331 | void selectAll()
332 | {
333 | foreach(ref sel; _selectionSet.selections)
334 | {
335 | sel.anchor = begin();
336 | sel.edge = end();
337 | }
338 | _selectionSet.normalize();
339 | }
340 |
341 | Cursor clampCursor(Cursor cursor)
342 | {
343 | Cursor result;
344 | result.line = min(max(cursor.line, 0), numLines() - 1);
345 | result.column = min(max(cursor.column, 0), maxColumn(result.line));
346 | return result;
347 | }
348 |
349 | // Add a new area-less selection
350 | void extendSelectionVertical(int dy)
351 | {
352 | Selection sel;
353 | if (dy > 0)
354 | sel = _selectionSet.selections[$-1];
355 | else
356 | sel = _selectionSet.selections[0];
357 |
358 | sel.edge.cursor = clampCursor(Cursor(sel.edge.cursor.line + dy, sel.edge.cursor.column));
359 |
360 | sel.anchor = sel.edge;
361 | _selectionSet.selections ~= sel;
362 | _selectionSet.normalize();
363 | }
364 |
365 | void addNewSelection(int line, int column, bool keepExistingSelections)
366 | {
367 | BufferIterator it = BufferIterator(this, clampCursor(Cursor(line, column)));
368 | Selection newSel = Selection(it, it);
369 | assert(newSel.isValid);
370 |
371 | if (keepExistingSelections)
372 | _selectionSet.selections ~= newSel;
373 | else
374 | _selectionSet.selections = [ newSel ];
375 | _selectionSet.normalize();
376 | }
377 |
378 | void moveToLineBegin(bool shift)
379 | {
380 | foreach(ref sel; _selectionSet.selections)
381 | {
382 | sel.edge.cursor.column = 0;
383 |
384 | if (!shift)
385 | sel.anchor = sel.edge;
386 | }
387 | _selectionSet.normalize();
388 | }
389 |
390 | void moveToLineEnd(bool shift)
391 | {
392 | foreach(ref sel; _selectionSet.selections)
393 | {
394 | sel.edge.cursor.column = maxColumn(sel.edge.cursor.line);
395 |
396 | if (!shift)
397 | sel.anchor = sel.edge;
398 | }
399 | _selectionSet.normalize();
400 | }
401 |
402 | inout(SelectionSet) selectionSet() inout
403 | {
404 | return _selectionSet;
405 | }
406 |
407 | void insertChar(dchar ch)
408 | {
409 | dstring content = ""d ~ ch;
410 | bySelectionEdit( (int i)
411 | {
412 | return content;
413 | } );
414 | }
415 |
416 | void insertTab()
417 | {
418 | dstring content = " "d;
419 | bySelectionEdit( (int i)
420 | {
421 | return content;
422 | } );
423 | }
424 |
425 | // selection with area => delete selection
426 | // else delete character at cursor or before cursor
427 | void deleteSelection(bool isBackspace)
428 | {
429 | enqueueBarrier();
430 | enqueueSaveSelections();
431 |
432 | for (int i = 0; i < _selectionSet.selections.length; ++i)
433 | {
434 | Selection selectionBeforeEdit = _selectionSet.selections[i];
435 | Selection selectionAfterEdit;
436 | if (selectionBeforeEdit.hasSelectedArea())
437 | {
438 | selectionAfterEdit = enqueueEdit(selectionBeforeEdit, ""d);
439 | }
440 | else
441 | {
442 | Selection oneCharSel = selectionBeforeEdit;
443 | if (isBackspace && oneCharSel.anchor.canBeDecremented)
444 | oneCharSel.anchor--;
445 | else if (oneCharSel.edge.canBeIncremented)
446 | oneCharSel.edge++;
447 |
448 | assert(oneCharSel.isValid());
449 | if (oneCharSel.hasSelectedArea())
450 | {
451 | selectionBeforeEdit = oneCharSel;
452 | selectionAfterEdit = enqueueEdit(oneCharSel, ""d);
453 | }
454 | else
455 | selectionAfterEdit = selectionBeforeEdit;
456 | }
457 |
458 | _selectionSet.selections[i] = selectionAfterEdit;
459 |
460 | // apply offset to all subsequent selections
461 | for (int j = i + 1; j < _selectionSet.selections.length; ++j)
462 | {
463 | _selectionSet.selections[j].translateByEdit(selectionBeforeEdit.sorted.edge, selectionAfterEdit.sorted.edge);
464 | }
465 |
466 | for (int j = 0; j < _selectionSet.selections.length; ++j)
467 | {
468 | assert(_selectionSet.selections[j].isValid());
469 | }
470 | }
471 | _selectionSet.keepOnlyEdge();
472 | _selectionSet.normalize();
473 | enqueueSaveSelections();
474 | }
475 |
476 | string filePath()
477 | {
478 | if (_filepath is null)
479 | return "Untitled";
480 | else
481 | return _filepath;
482 | }
483 |
484 | void cleanup()
485 | {
486 | enqueueBarrier();
487 | enqueueSaveSelections();
488 |
489 | _selectionSet.keepOnlyFirst();
490 | _selectionSet.selections[0].edge.cursor = Cursor(0, 0);
491 | _selectionSet.selections[0].anchor.cursor = Cursor(0, 0);
492 |
493 | for (int i = 0; i < lines.length; ++i)
494 | {
495 | bool whitespace = true;
496 | int start = i == lines.length - 1 ? cast(int)(lines[i].length) - 1 : cast(int)(lines[i].length) - 2;
497 | for (int j = start; j >= 0; --j)
498 | {
499 | dchar ch = lines[i][j];
500 | if (whitespace && ch == ' ')
501 | {
502 | Selection sel = Selection(BufferIterator(this, Cursor(i, j)), BufferIterator(this, Cursor(i, j + 1)));
503 | enqueueEdit(sel, ""d);
504 | }
505 | else
506 | whitespace = false;
507 |
508 | if (ch == '\t')
509 | {
510 | Selection sel = Selection(BufferIterator(this, Cursor(i, j)), BufferIterator(this, Cursor(i, j + 1)));
511 | enqueueEdit(sel, " "d);
512 | }
513 | }
514 | }
515 | _selectionSet.normalize();
516 | enqueueSaveSelections();
517 | }
518 |
519 | struct Hit
520 | {
521 | bool charInSelection;
522 | bool cursorThere;
523 | Selection selInside;
524 | }
525 |
526 | // return a combination of Hit flags
527 | Hit intersectsSelection(Cursor cursor)
528 | {
529 | Hit hit;
530 | hit.charInSelection = false;
531 | hit.cursorThere = false;
532 |
533 | int min = 0;
534 | int max = cast(int) _selectionSet.selections.length - 1;
535 | while(min <= max) // assume sorted selections
536 | {
537 | int middle = (min + max + 1) / 2;
538 | Selection sel = _selectionSet.selections[middle];
539 | Selection ssel = sel.sorted();
540 |
541 | if (cursor == sel.edge.cursor)
542 | hit.cursorThere = true;
543 |
544 | if (cursor < ssel.anchor.cursor)
545 | {
546 | max = middle - 1;
547 | }
548 | else if (cursor >= ssel.edge.cursor)
549 | {
550 | min = middle + 1;
551 | }
552 | else if (cursor >= ssel.anchor.cursor && cursor < ssel.edge.cursor)
553 | {
554 | hit.charInSelection = true;
555 | hit.selInside = sel;
556 | break;
557 | }
558 | else
559 | assert(false);
560 | }
561 | return hit;
562 | }
563 |
564 |
565 | invariant()
566 | {
567 | if (_hasBeenLoaded)
568 | {
569 | // at least one line
570 | if (lines.length < 1)
571 | {
572 | assert(false);
573 | }
574 |
575 | // every line finish by a \n
576 | for (size_t i = 0; i < lines.length; ++i)
577 | {
578 | dstring l = lines[i];
579 | if (l.length == 0)
580 | {
581 | assert(false);
582 | }
583 | if (l[$-1] != '\n')
584 | {
585 | assert(false);
586 | }
587 | }
588 | }
589 | }
590 |
591 | package:
592 |
593 | BufferIterator begin()
594 | {
595 | return BufferIterator(this, Cursor(0, 0));
596 | }
597 |
598 | BufferIterator end()
599 | {
600 | return BufferIterator(this, Cursor(cast(int) lines.length - 1, maxColumn(cast(int) lines.length - 1)));
601 | }
602 |
603 | private:
604 |
605 | void updateLongestLine() pure nothrow
606 | {
607 | int maxLength = 0;
608 | _longestLine = 0;
609 |
610 | for (int i = 0; i < cast(int)lines.length; ++i)
611 | {
612 | if (lines[i].length > maxLength)
613 | {
614 | maxLength = cast(int) lines[i].length;
615 | _longestLine = i;
616 | }
617 | }
618 | }
619 |
620 | void pushCommand(BufferCommand command)
621 | {
622 | // strip previous history, add command
623 | _history = _history[0.._historyIndex] ~ command;
624 | _historyIndex = cast(int) _history.length;
625 | }
626 |
627 | // replace a Selection content by a new content
628 | // returns a cursor Selection just after the newly inserted part
629 | Selection replaceSelectionContent(Selection selection, dstring content)
630 | {
631 | Selection sel = selection.sorted();
632 | erase(sel.anchor, sel.edge);
633 | BufferIterator after = insert(sel.anchor, content);
634 | return Selection(sel.anchor, after);
635 | }
636 |
637 | // Gets content of a selection
638 | dstring getSelectionContent(Selection selection)
639 | {
640 | Selection sel = selection.sorted();
641 | dstring result;
642 |
643 | // TODO PERF this could be faster by appending larger chunks
644 | for (BufferIterator it = sel.anchor; it != sel.edge; ++it)
645 | result ~= it.read();
646 |
647 | return result;
648 | }
649 |
650 | BufferIterator insert(BufferIterator pos, dstring content)
651 | {
652 | foreach(dchar ch ; content)
653 | pos = insert(pos, ch);
654 | return pos;
655 | }
656 |
657 | // insert a single char
658 | // return an iterator after the inserted char
659 | BufferIterator insert(BufferIterator pos, dchar content)
660 | {
661 | assert(pos.isValid());
662 |
663 | if (content == '\n')
664 | {
665 | int col = pos.cursor.column;
666 | int line = pos.cursor.line;
667 | bool shouldUpdateLongestLine = (line == _longestLine);
668 | bool shouldAddOneToLongestLine = (line < _longestLine);
669 | dstring thisLine = lines[line];
670 | lines.insertInPlace(line, thisLine[0..col].idup ~ '\n'); // copy sub-part addind a \n
671 | lines[line + 1] = lines[line + 1][col..$]; // next line becomes everything else
672 |
673 | // in case we broke the longest line, linear search of longest line
674 | if (shouldAddOneToLongestLine)
675 | _longestLine++;
676 |
677 | if (shouldUpdateLongestLine)
678 | updateLongestLine();
679 |
680 | return BufferIterator(pos.buffer, Cursor(line + 1, 0));
681 | }
682 | else
683 | {
684 | int line = pos.cursor.line;
685 | int column = pos.cursor.column;
686 | dstring oneCh = (&content)[0..1].idup;
687 | replaceInPlace(lines[line], column, column, oneCh);
688 |
689 | // check that longest line has changed
690 | if (lines[line].length > getLongestLineLength())
691 | _longestLine = line;
692 |
693 | return BufferIterator(pos.buffer, Cursor(line, column + 1));
694 | }
695 | }
696 |
697 | // delete a single char
698 | void erase(BufferIterator pos)
699 | {
700 | dchar chErased = pos.read();
701 | if (chErased == '\n')
702 | {
703 | int line = pos.cursor.line;
704 | int column = pos.cursor.column;
705 | dstring newLine = lines[line][0..$-1] ~ lines[line+1];
706 | replaceInPlace(lines, line, line + 2, [ newLine ]);
707 | updateLongestLine();
708 | }
709 | else
710 | {
711 | int line = pos.cursor.line;
712 | int column = pos.cursor.column;
713 | replaceInPlace(lines[line], column, column + 1, ""d);
714 |
715 | // check that it's still the longest line
716 | if (_longestLine == line)
717 | updateLongestLine();
718 | }
719 | }
720 |
721 | void erase(BufferIterator begin, BufferIterator end)
722 | {
723 | while (begin < end)
724 | {
725 | --end;
726 | erase(end);
727 | }
728 | }
729 |
730 | void applyCommand(BufferCommand command)
731 | {
732 | final switch(command.type) with (BufferCommandType)
733 | {
734 | case CHANGE_CHARS:
735 | replaceSelectionContent(command.changeChars.oldSel, command.changeChars.newContent);
736 | break;
737 |
738 | case SAVE_SELECTIONS:
739 | // also restore them, useful in redo sequences
740 | _selectionSet.selections = command.saveSelections.selections.dup;
741 | break;
742 |
743 | case BARRIER:
744 | // do nothing
745 | break;
746 | }
747 | }
748 |
749 | // returns true if it was a barrier
750 | bool reverseCommand(BufferCommand command)
751 | {
752 | final switch(command.type) with (BufferCommandType)
753 | {
754 | case CHANGE_CHARS:
755 | replaceSelectionContent(command.changeChars.newSel, command.changeChars.oldContent);
756 | return false;
757 |
758 | case SAVE_SELECTIONS:
759 | // restore selections
760 | _selectionSet.selections = command.saveSelections.selections.dup;
761 | return false;
762 |
763 | case BARRIER:
764 | return true;
765 | }
766 | }
767 |
768 | void enqueueBarrier()
769 | {
770 | pushCommand(barrierCommand());
771 | }
772 |
773 | void enqueueSaveSelections()
774 | {
775 | pushCommand(saveSelectionsCommand(_selectionSet.selections));
776 | }
777 |
778 | Selection enqueueEdit(Selection selection, dstring newContent)
779 | {
780 | dstring oldContent = getSelectionContent(selection);
781 | Selection newSel = replaceSelectionContent(selection, newContent);
782 | BufferCommand command = changeCharsCommand(selection, newSel, oldContent, newContent);
783 | pushCommand(command);
784 | return Selection(newSel.edge);
785 | }
786 |
787 | void bySelectionEdit(dstring delegate(int i) selectionContent)
788 | {
789 | enqueueBarrier();
790 | enqueueSaveSelections();
791 |
792 | for (int i = 0; i < _selectionSet.selections.length; ++i)
793 | {
794 | Selection selectionBeforeEdit = _selectionSet.selections[i];
795 | Selection selectionAfterEdit = enqueueEdit(selectionBeforeEdit, selectionContent(i)).sorted();
796 |
797 | // apply offset to all subsequent selections
798 | // TODO PERF this is quadratic behaviour
799 | for (int j = i + 1; j < _selectionSet.selections.length; ++j)
800 | {
801 | _selectionSet.selections[j].translateByEdit(selectionBeforeEdit.sorted.edge, selectionAfterEdit.sorted.edge);
802 | }
803 | _selectionSet.selections[i] = selectionAfterEdit;
804 | }
805 | _selectionSet.normalize();
806 | enqueueSaveSelections();
807 | }
808 | }
809 |
810 | private
811 | {
812 |
813 |
814 | // removes BOM, sanitize Unicode, and split on line endings
815 | dstring[] readTextFile(string path, out int longestLine)
816 | {
817 | string wholeFile = cast(string) std.file.read(path);
818 |
819 | // remove UTF-8 BOM
820 | if (wholeFile.length > 3 && wholeFile[0] == '\xEF' && wholeFile[1] == '\xBB' && wholeFile[2] == '\xBF')
821 | wholeFile = wholeFile[3..$];
822 |
823 | // sanitize non-UTF-8 sequences
824 | import std.encoding : sanitize;
825 | wholeFile = sanitize(wholeFile);
826 |
827 | dstring wholeFileUTF32 = to!dstring(wholeFile);
828 |
829 | dstring[] lines;
830 | dstring currentLine;
831 | longestLine = 0;
832 | int maxLength = 0;
833 | int numLine = 0;
834 |
835 | for (size_t i = 0; i < wholeFileUTF32.length; ++i)
836 | {
837 | dchar ch = wholeFileUTF32[i];
838 |
839 | if (ch == '\n')
840 | {
841 | currentLine ~= '\n';
842 | if (currentLine.length > maxLength)
843 | {
844 | longestLine = numLine;
845 | maxLength = cast(int) currentLine.length;
846 | }
847 | lines ~= currentLine.dup;
848 | numLine++;
849 | currentLine.length = 0;
850 | }
851 | else if (ch == '\r')
852 | {
853 | // simply remove them
854 | }
855 | else
856 | {
857 | currentLine ~= ch;
858 | }
859 | }
860 |
861 | // last line must have a virtual line-feed, this simplify things
862 | currentLine ~= '\n';
863 | lines ~= currentLine.dup;
864 | numLine++;
865 | return lines;
866 | }
867 | }
868 |
--------------------------------------------------------------------------------
/source/dido/pngload.d:
--------------------------------------------------------------------------------
1 | /// D translation of stb_image-1.33 (http://nothings.org/stb_image.c)
2 | ///
3 | /// This port only supports:
4 | /// $(UL
5 | /// $(LI PNG 8-bit-per-channel only.)
6 | /// )
7 | ///
8 | //============================ Contributors =========================
9 | //
10 | // Image formats Optimizations & bugfixes
11 | // Sean Barrett (jpeg, png, bmp) Fabian "ryg" Giesen
12 | // Nicolas Schulz (hdr, psd)
13 | // Jonathan Dummer (tga) Bug fixes & warning fixes
14 | // Jean-Marc Lienher (gif) Marc LeBlanc
15 | // Tom Seddon (pic) Christpher Lloyd
16 | // Thatcher Ulrich (psd) Dave Moore
17 | // Won Chun
18 | // the Horde3D community
19 | // Extensions, features Janez Zemva
20 | // Jetro Lauha (stbi_info) Jonathan Blow
21 | // James "moose2000" Brown (iPhone PNG) Laurent Gomila
22 | // Ben "Disch" Wenger (io callbacks) Aruelien Pocheville
23 | // Martin "SpartanJ" Golini Ryamond Barbiero
24 | // David Woo
25 | module dido.pngload;
26 |
27 | // This has been revived for the sake of loading PNG without too much memory usage.
28 | // It turns out stb_image is more efficient than the loaders using std.zlib.
29 | // https://github.com/lgvz/imageformats/issues/26
30 |
31 | import core.stdc.stdlib;
32 | import core.stdc.string;
33 |
34 | enum STBI_VERSION = 1;
35 |
36 | nothrow:
37 | @nogc:
38 |
39 | enum : int
40 | {
41 | STBI_default = 0, // only used for req_comp
42 | STBI_grey = 1,
43 | STBI_grey_alpha = 2,
44 | STBI_rgb = 3,
45 | STBI_rgb_alpha = 4
46 | };
47 |
48 | // define faster low-level operations (typically SIMD support)
49 |
50 | // stbi structure is our basic context used by all images, so it
51 | // contains all the IO context, plus some basic image information
52 | struct stbi
53 | {
54 | uint img_x, img_y;
55 | int img_n, img_out_n;
56 |
57 | int buflen;
58 | ubyte[128] buffer_start;
59 |
60 | const(ubyte) *img_buffer;
61 | const(ubyte) *img_buffer_end;
62 | const(ubyte) *img_buffer_original;
63 | }
64 |
65 |
66 | // initialize a memory-decode context
67 | void start_mem(stbi *s, const(ubyte)*buffer, int len)
68 | {
69 | s.img_buffer = buffer;
70 | s.img_buffer_original = buffer;
71 | s.img_buffer_end = buffer+len;
72 | }
73 |
74 | /// Loads an image from memory.
75 | /// Throws: STBImageException on error.
76 | ubyte* stbi_load_png_from_memory(const(void)[] buffer, out int width, out int height, out int components, int requestedComponents)
77 | {
78 | stbi s;
79 | start_mem(&s, cast(const(ubyte)*)buffer.ptr, cast(int)(buffer.length));
80 | return stbi_png_load(&s, &width, &height, &components, requestedComponents);
81 | }
82 |
83 |
84 | //
85 | // Common code used by all image loaders
86 | //
87 |
88 | enum : int
89 | {
90 | SCAN_load=0,
91 | SCAN_type,
92 | SCAN_header
93 | };
94 |
95 |
96 | int get8(stbi *s)
97 | {
98 | if (s.img_buffer < s.img_buffer_end)
99 | return *s.img_buffer++;
100 |
101 | return 0;
102 | }
103 |
104 | int at_eof(stbi *s)
105 | {
106 | return s.img_buffer >= s.img_buffer_end;
107 | }
108 |
109 | ubyte get8u(stbi *s)
110 | {
111 | return cast(ubyte) get8(s);
112 | }
113 |
114 | void skip(stbi *s, int n)
115 | {
116 | s.img_buffer += n;
117 | }
118 |
119 | int getn(stbi *s, ubyte *buffer, int n)
120 | {
121 | if (s.img_buffer+n <= s.img_buffer_end) {
122 | memcpy(buffer, s.img_buffer, n);
123 | s.img_buffer += n;
124 | return 1;
125 | } else
126 | return 0;
127 | }
128 |
129 | int get16(stbi *s)
130 | {
131 | int z = get8(s);
132 | return (z << 8) + get8(s);
133 | }
134 |
135 | uint get32(stbi *s)
136 | {
137 | uint z = get16(s);
138 | return (z << 16) + get16(s);
139 | }
140 |
141 | int get16le(stbi *s)
142 | {
143 | int z = get8(s);
144 | return z + (get8(s) << 8);
145 | }
146 |
147 | uint get32le(stbi *s)
148 | {
149 | uint z = get16le(s);
150 | return z + (get16le(s) << 16);
151 | }
152 |
153 | //
154 | // generic converter from built-in img_n to req_comp
155 | // individual types do this automatically as much as possible (e.g. jpeg
156 | // does all cases internally since it needs to colorspace convert anyway,
157 | // and it never has alpha, so very few cases ). png can automatically
158 | // interleave an alpha=255 channel, but falls back to this for other cases
159 | //
160 | // assume data buffer is malloced, so malloc a new one and free that one
161 | // only failure mode is malloc failing
162 |
163 | ubyte compute_y(int r, int g, int b)
164 | {
165 | return cast(ubyte) (((r*77) + (g*150) + (29*b)) >> 8);
166 | }
167 |
168 | ubyte *convert_format(ubyte *data, int img_n, int req_comp, uint x, uint y)
169 | {
170 | int i,j;
171 | ubyte *good;
172 |
173 | if (req_comp == img_n) return data;
174 | assert(req_comp >= 1 && req_comp <= 4);
175 |
176 | good = cast(ubyte*) malloc(req_comp * x * y);
177 | if (good == null) {
178 | free(data);
179 | assert(false);
180 | }
181 |
182 | for (j=0; j < cast(int) y; ++j) {
183 | ubyte *src = data + j * x * img_n ;
184 | ubyte *dest = good + j * x * req_comp;
185 |
186 | // convert source image with img_n components to one with req_comp components;
187 | // avoid switch per pixel, so use switch per scanline and massive macros
188 | switch (img_n * 8 + req_comp)
189 | {
190 | case 1 * 8 + 2:
191 | for(i=x-1; i >= 0; --i, src += 1, dest += 2)
192 | dest[0] = src[0], dest[1] = 255;
193 | break;
194 | case 1 * 8 + 3:
195 | for(i=x-1; i >= 0; --i, src += 1, dest += 3)
196 | dest[0]=dest[1]=dest[2]=src[0];
197 | break;
198 | case 1 * 8 + 4:
199 | for(i=x-1; i >= 0; --i, src += 1, dest += 4)
200 | dest[0]=dest[1]=dest[2]=src[0], dest[3]=255;
201 | break;
202 | case 2 * 8 + 1:
203 | for(i=x-1; i >= 0; --i, src += 2, dest += 1)
204 | dest[0]=src[0];
205 | break;
206 | case 2 * 8 + 3:
207 | for(i=x-1; i >= 0; --i, src += 2, dest += 3)
208 | dest[0]=dest[1]=dest[2]=src[0];
209 | break;
210 | case 2 * 8 + 4:
211 | for(i=x-1; i >= 0; --i, src += 2, dest += 4)
212 | dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1];
213 | break;
214 | case 3 * 8 + 4:
215 | for(i=x-1; i >= 0; --i, src += 3, dest += 4)
216 | dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255;
217 | break;
218 | case 3 * 8 + 1:
219 | for(i=x-1; i >= 0; --i, src += 3, dest += 1)
220 | dest[0]=compute_y(src[0],src[1],src[2]);
221 | break;
222 | case 3 * 8 + 2:
223 | for(i=x-1; i >= 0; --i, src += 3, dest += 2)
224 | dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = 255;
225 | break;
226 | case 4 * 8 + 1:
227 | for(i=x-1; i >= 0; --i, src += 4, dest += 1)
228 | dest[0]=compute_y(src[0],src[1],src[2]);
229 | break;
230 | case 4 * 8 + 2:
231 | for(i=x-1; i >= 0; --i, src += 4, dest += 2)
232 | dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = src[3];
233 | break;
234 | case 4 * 8 + 3:
235 | for(i=x-1; i >= 0; --i, src += 4, dest += 3)
236 | dest[0]=src[0],dest[1]=src[1],dest[2]=src[2];
237 | break;
238 | default: assert(0);
239 | }
240 | }
241 |
242 | free(data);
243 | return good;
244 | }
245 |
246 | // public domain zlib decode v0.2 Sean Barrett 2006-11-18
247 | // simple implementation
248 | // - all input must be provided in an upfront buffer
249 | // - all output is written to a single output buffer (can malloc/realloc)
250 | // performance
251 | // - fast huffman
252 |
253 | // fast-way is faster to check than jpeg huffman, but slow way is slower
254 | enum ZFAST_BITS = 9; // accelerate all cases in default tables
255 | enum ZFAST_MASK = ((1 << ZFAST_BITS) - 1);
256 |
257 | // zlib-style huffman encoding
258 | // (jpegs packs from left, zlib from right, so can't share code)
259 | struct zhuffman
260 | {
261 | ushort[1 << ZFAST_BITS] fast;
262 | ushort[16] firstcode;
263 | int[17] maxcode;
264 | ushort[16] firstsymbol;
265 | ubyte[288] size;
266 | ushort[288] value;
267 | } ;
268 |
269 | int bitreverse16(int n)
270 | {
271 | n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1);
272 | n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2);
273 | n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4);
274 | n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8);
275 | return n;
276 | }
277 |
278 | int bit_reverse(int v, int bits)
279 | {
280 | assert(bits <= 16);
281 | // to bit reverse n bits, reverse 16 and shift
282 | // e.g. 11 bits, bit reverse and shift away 5
283 | return bitreverse16(v) >> (16-bits);
284 | }
285 |
286 | int zbuild_huffman(zhuffman *z, ubyte *sizelist, int num)
287 | {
288 | int i,k=0;
289 | int code;
290 | int[16] next_code;
291 | int[17] sizes;
292 |
293 | // DEFLATE spec for generating codes
294 | memset(sizes.ptr, 0, sizes.sizeof);
295 | memset(z.fast.ptr, 255, z.fast.sizeof);
296 | for (i=0; i < num; ++i)
297 | ++sizes[sizelist[i]];
298 | sizes[0] = 0;
299 | for (i=1; i < 16; ++i)
300 | assert(sizes[i] <= (1 << i));
301 | code = 0;
302 | for (i=1; i < 16; ++i) {
303 | next_code[i] = code;
304 | z.firstcode[i] = cast(ushort) code;
305 | z.firstsymbol[i] = cast(ushort) k;
306 | code = (code + sizes[i]);
307 | if (sizes[i])
308 | if (code-1 >= (1 << i))
309 | assert(false);
310 | z.maxcode[i] = code << (16-i); // preshift for inner loop
311 | code <<= 1;
312 | k += sizes[i];
313 | }
314 | z.maxcode[16] = 0x10000; // sentinel
315 | for (i=0; i < num; ++i) {
316 | int s = sizelist[i];
317 | if (s) {
318 | int c = next_code[s] - z.firstcode[s] + z.firstsymbol[s];
319 | z.size[c] = cast(ubyte)s;
320 | z.value[c] = cast(ushort)i;
321 | if (s <= ZFAST_BITS) {
322 | int k_ = bit_reverse(next_code[s],s);
323 | while (k_ < (1 << ZFAST_BITS)) {
324 | z.fast[k_] = cast(ushort) c;
325 | k_ += (1 << s);
326 | }
327 | }
328 | ++next_code[s];
329 | }
330 | }
331 | return 1;
332 | }
333 |
334 | // zlib-from-memory implementation for PNG reading
335 | // because PNG allows splitting the zlib stream arbitrarily,
336 | // and it's annoying structurally to have PNG call ZLIB call PNG,
337 | // we require PNG read all the IDATs and combine them into a single
338 | // memory buffer
339 |
340 | struct zbuf
341 | {
342 | const(ubyte) *zbuffer;
343 | const(ubyte) *zbuffer_end;
344 | int num_bits;
345 | uint code_buffer;
346 |
347 | ubyte *zout;
348 | ubyte *zout_start;
349 | ubyte *zout_end;
350 | int z_expandable;
351 |
352 | zhuffman z_length, z_distance;
353 | } ;
354 |
355 | int zget8(zbuf *z)
356 | {
357 | if (z.zbuffer >= z.zbuffer_end) return 0;
358 | return *z.zbuffer++;
359 | }
360 |
361 | void fill_bits(zbuf *z)
362 | {
363 | do {
364 | assert(z.code_buffer < (1U << z.num_bits));
365 | z.code_buffer |= zget8(z) << z.num_bits;
366 | z.num_bits += 8;
367 | } while (z.num_bits <= 24);
368 | }
369 |
370 | uint zreceive(zbuf *z, int n)
371 | {
372 | uint k;
373 | if (z.num_bits < n) fill_bits(z);
374 | k = z.code_buffer & ((1 << n) - 1);
375 | z.code_buffer >>= n;
376 | z.num_bits -= n;
377 | return k;
378 | }
379 |
380 | int zhuffman_decode(zbuf *a, zhuffman *z)
381 | {
382 | int b,s,k;
383 | if (a.num_bits < 16) fill_bits(a);
384 | b = z.fast[a.code_buffer & ZFAST_MASK];
385 | if (b < 0xffff) {
386 | s = z.size[b];
387 | a.code_buffer >>= s;
388 | a.num_bits -= s;
389 | return z.value[b];
390 | }
391 |
392 | // not resolved by fast table, so compute it the slow way
393 | // use jpeg approach, which requires MSbits at top
394 | k = bit_reverse(a.code_buffer, 16);
395 | for (s=ZFAST_BITS+1; ; ++s)
396 | if (k < z.maxcode[s])
397 | break;
398 | if (s == 16) return -1; // invalid code!
399 | // code size is s, so:
400 | b = (k >> (16-s)) - z.firstcode[s] + z.firstsymbol[s];
401 | assert(z.size[b] == s);
402 | a.code_buffer >>= s;
403 | a.num_bits -= s;
404 | return z.value[b];
405 | }
406 |
407 | int expand(zbuf *z, int n) // need to make room for n bytes
408 | {
409 | ubyte *q;
410 | int cur, limit;
411 | if (!z.z_expandable)
412 | assert(false, "Output buffer limit, corrupt PNG");
413 | cur = cast(int) (z.zout - z.zout_start);
414 | limit = cast(int) (z.zout_end - z.zout_start);
415 | while (cur + n > limit)
416 | limit *= 2;
417 | q = cast(ubyte*) realloc(z.zout_start, limit);
418 | if (q == null)
419 | assert(false, "Out of memory");
420 | z.zout_start = q;
421 | z.zout = q + cur;
422 | z.zout_end = q + limit;
423 | return 1;
424 | }
425 |
426 | static immutable int[31] length_base = [
427 | 3,4,5,6,7,8,9,10,11,13,
428 | 15,17,19,23,27,31,35,43,51,59,
429 | 67,83,99,115,131,163,195,227,258,0,0 ];
430 |
431 | static immutable int[31] length_extra =
432 | [ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 ];
433 |
434 | static immutable int[32] dist_base = [ 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,
435 | 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0];
436 |
437 | static immutable int[32] dist_extra =
438 | [ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];
439 |
440 | int parse_huffman_block(zbuf *a)
441 | {
442 | for(;;) {
443 | int z = zhuffman_decode(a, &a.z_length);
444 | if (z < 256) {
445 | if (z < 0)
446 | assert(false, "Bad Huffman code, corrupt PNG");
447 | if (a.zout >= a.zout_end) if (!expand(a, 1)) return 0;
448 | *a.zout++ = cast(ubyte) z;
449 | } else {
450 | ubyte *p;
451 | int len,dist;
452 | if (z == 256) return 1;
453 | z -= 257;
454 | len = length_base[z];
455 | if (length_extra[z]) len += zreceive(a, length_extra[z]);
456 | z = zhuffman_decode(a, &a.z_distance);
457 | if (z < 0) assert(false, "Bad Huffman code, corrupt PNG");
458 | dist = dist_base[z];
459 | if (dist_extra[z]) dist += zreceive(a, dist_extra[z]);
460 | if (a.zout - a.zout_start < dist) assert(false, "Bad dist, corrupt PNG");
461 | if (a.zout + len > a.zout_end) if (!expand(a, len)) return 0;
462 | p = a.zout - dist;
463 | while (len--)
464 | *a.zout++ = *p++;
465 | }
466 | }
467 | }
468 |
469 | int compute_huffman_codes(zbuf *a)
470 | {
471 | static immutable ubyte[19] length_dezigzag = [ 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 ];
472 | zhuffman z_codelength;
473 | ubyte[286+32+137] lencodes;//padding for maximum single op
474 | ubyte[19] codelength_sizes;
475 | int i,n;
476 |
477 | int hlit = zreceive(a,5) + 257;
478 | int hdist = zreceive(a,5) + 1;
479 | int hclen = zreceive(a,4) + 4;
480 |
481 | memset(codelength_sizes.ptr, 0, codelength_sizes.sizeof);
482 | for (i=0; i < hclen; ++i) {
483 | int s = zreceive(a,3);
484 | codelength_sizes[length_dezigzag[i]] = cast(ubyte) s;
485 | }
486 | if (!zbuild_huffman(&z_codelength, codelength_sizes.ptr, 19)) return 0;
487 |
488 | n = 0;
489 | while (n < hlit + hdist) {
490 | int c = zhuffman_decode(a, &z_codelength);
491 | assert(c >= 0 && c < 19);
492 | if (c < 16)
493 | lencodes[n++] = cast(ubyte) c;
494 | else if (c == 16) {
495 | c = zreceive(a,2)+3;
496 | memset(lencodes.ptr+n, lencodes[n-1], c);
497 | n += c;
498 | } else if (c == 17) {
499 | c = zreceive(a,3)+3;
500 | memset(lencodes.ptr+n, 0, c);
501 | n += c;
502 | } else {
503 | assert(c == 18);
504 | c = zreceive(a,7)+11;
505 | memset(lencodes.ptr+n, 0, c);
506 | n += c;
507 | }
508 | }
509 | if (n != hlit+hdist) assert(false, "Bad codelengths, corrupt PNG");
510 | if (!zbuild_huffman(&a.z_length, lencodes.ptr, hlit)) return 0;
511 | if (!zbuild_huffman(&a.z_distance, lencodes.ptr+hlit, hdist)) return 0;
512 | return 1;
513 | }
514 |
515 | int parse_uncompressed_block(zbuf *a)
516 | {
517 | ubyte[4] header;
518 | int len,nlen,k;
519 | if (a.num_bits & 7)
520 | zreceive(a, a.num_bits & 7); // discard
521 | // drain the bit-packed data into header
522 | k = 0;
523 | while (a.num_bits > 0) {
524 | header[k++] = cast(ubyte) (a.code_buffer & 255); // wtf this warns?
525 | a.code_buffer >>= 8;
526 | a.num_bits -= 8;
527 | }
528 | assert(a.num_bits == 0);
529 | // now fill header the normal way
530 | while (k < 4)
531 | header[k++] = cast(ubyte) zget8(a);
532 | len = header[1] * 256 + header[0];
533 | nlen = header[3] * 256 + header[2];
534 | if (nlen != (len ^ 0xffff)) assert(false, "Zlib corrupt, corrupt PNG");
535 | if (a.zbuffer + len > a.zbuffer_end) assert(false, "Read past buffer, corrupt PNG");
536 | if (a.zout + len > a.zout_end)
537 | if (!expand(a, len)) return 0;
538 | memcpy(a.zout, a.zbuffer, len);
539 | a.zbuffer += len;
540 | a.zout += len;
541 | return 1;
542 | }
543 |
544 | int parse_zlib_header(zbuf *a)
545 | {
546 | int cmf = zget8(a);
547 | int cm = cmf & 15;
548 | /* int cinfo = cmf >> 4; */
549 | int flg = zget8(a);
550 | if ((cmf*256+flg) % 31 != 0) assert(false, "Bad zlib header, corrupt PNG"); // zlib spec
551 | if (flg & 32) assert(false, "No preset dict, corrupt PNG"); // preset dictionary not allowed in png
552 | if (cm != 8) assert(false, "Bad compression, corrupt PNG"); // DEFLATE required for png
553 | // window = 1 << (8 + cinfo)... but who cares, we fully buffer output
554 | return 1;
555 | }
556 |
557 | // MAYDO: should statically initialize these for optimal thread safety
558 | __gshared ubyte[288] default_length;
559 | __gshared ubyte[32] default_distance;
560 |
561 | void init_defaults()
562 | {
563 | int i; // use <= to match clearly with spec
564 | for (i=0; i <= 143; ++i) default_length[i] = 8;
565 | for ( ; i <= 255; ++i) default_length[i] = 9;
566 | for ( ; i <= 279; ++i) default_length[i] = 7;
567 | for ( ; i <= 287; ++i) default_length[i] = 8;
568 |
569 | for (i=0; i <= 31; ++i) default_distance[i] = 5;
570 | }
571 |
572 | __gshared int stbi_png_partial; // a quick hack to only allow decoding some of a PNG... I should implement real streaming support instead
573 | int parse_zlib(zbuf *a, int parse_header)
574 | {
575 | int final_, type;
576 | if (parse_header)
577 | if (!parse_zlib_header(a)) return 0;
578 | a.num_bits = 0;
579 | a.code_buffer = 0;
580 | do {
581 | final_ = zreceive(a,1);
582 | type = zreceive(a,2);
583 | if (type == 0) {
584 | if (!parse_uncompressed_block(a)) return 0;
585 | } else if (type == 3) {
586 | return 0;
587 | } else {
588 | if (type == 1) {
589 | // use fixed code lengths
590 | if (!default_distance[31]) init_defaults();
591 | if (!zbuild_huffman(&a.z_length , default_length.ptr , 288)) return 0;
592 | if (!zbuild_huffman(&a.z_distance, default_distance.ptr, 32)) return 0;
593 | } else {
594 | if (!compute_huffman_codes(a)) return 0;
595 | }
596 | if (!parse_huffman_block(a)) return 0;
597 | }
598 | if (stbi_png_partial && a.zout - a.zout_start > 65536)
599 | break;
600 | } while (!final_);
601 | return 1;
602 | }
603 |
604 | int do_zlib(zbuf *a, ubyte *obuf, int olen, int exp, int parse_header)
605 | {
606 | a.zout_start = obuf;
607 | a.zout = obuf;
608 | a.zout_end = obuf + olen;
609 | a.z_expandable = exp;
610 |
611 | return parse_zlib(a, parse_header);
612 | }
613 |
614 | ubyte *stbi_zlib_decode_malloc_guesssize(const(ubyte) *buffer, int len, int initial_size, int *outlen)
615 | {
616 | zbuf a;
617 | ubyte *p = cast(ubyte*) malloc(initial_size);
618 | if (p == null) return null;
619 | a.zbuffer = buffer;
620 | a.zbuffer_end = buffer + len;
621 | if (do_zlib(&a, p, initial_size, 1, 1)) {
622 | if (outlen) *outlen = cast(int) (a.zout - a.zout_start);
623 | return a.zout_start;
624 | } else {
625 | free(a.zout_start);
626 | return null;
627 | }
628 | }
629 |
630 | ubyte *stbi_zlib_decode_malloc(const(ubyte) *buffer, int len, int *outlen)
631 | {
632 | return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);
633 | }
634 |
635 | ubyte *stbi_zlib_decode_malloc_guesssize_headerflag(const(ubyte) *buffer, int len, int initial_size, int *outlen, int parse_header)
636 | {
637 | zbuf a;
638 | ubyte *p = cast(ubyte*) malloc(initial_size);
639 | if (p == null) return null;
640 | a.zbuffer = buffer;
641 | a.zbuffer_end = buffer + len;
642 | if (do_zlib(&a, p, initial_size, 1, parse_header)) {
643 | if (outlen) *outlen = cast(int) (a.zout - a.zout_start);
644 | return a.zout_start;
645 | } else {
646 | free(a.zout_start);
647 | return null;
648 | }
649 | }
650 |
651 | int stbi_zlib_decode_buffer(ubyte* obuffer, int olen, const(ubyte)* ibuffer, int ilen)
652 | {
653 | zbuf a;
654 | a.zbuffer = ibuffer;
655 | a.zbuffer_end = ibuffer + ilen;
656 | if (do_zlib(&a, obuffer, olen, 0, 1))
657 | return cast(int) (a.zout - a.zout_start);
658 | else
659 | return -1;
660 | }
661 |
662 | ubyte *stbi_zlib_decode_noheader_malloc(const(ubyte) *buffer, int len, int *outlen)
663 | {
664 | zbuf a;
665 | ubyte *p = cast(ubyte*) malloc(16384);
666 | if (p == null) return null;
667 | a.zbuffer = buffer;
668 | a.zbuffer_end = buffer+len;
669 | if (do_zlib(&a, p, 16384, 1, 0)) {
670 | if (outlen) *outlen = cast(int) (a.zout - a.zout_start);
671 | return a.zout_start;
672 | } else {
673 | free(a.zout_start);
674 | return null;
675 | }
676 | }
677 |
678 | int stbi_zlib_decode_noheader_buffer(ubyte *obuffer, int olen, const(ubyte) *ibuffer, int ilen)
679 | {
680 | zbuf a;
681 | a.zbuffer = ibuffer;
682 | a.zbuffer_end = ibuffer + ilen;
683 | if (do_zlib(&a, obuffer, olen, 0, 0))
684 | return cast(int) (a.zout - a.zout_start);
685 | else
686 | return -1;
687 | }
688 |
689 | // public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18
690 | // simple implementation
691 | // - only 8-bit samples
692 | // - no CRC checking
693 | // - allocates lots of intermediate memory
694 | // - avoids problem of streaming data between subsystems
695 | // - avoids explicit window management
696 | // performance
697 | // - uses stb_zlib, a PD zlib implementation with fast huffman decoding
698 |
699 |
700 | struct chunk
701 | {
702 | uint length;
703 | uint type;
704 | }
705 |
706 | uint PNG_TYPE(ubyte a, ubyte b, ubyte c, ubyte d)
707 | {
708 | return (a << 24) + (b << 16) + (c << 8) + d;
709 | }
710 |
711 | chunk get_chunk_header(stbi *s)
712 | {
713 | chunk c;
714 | c.length = get32(s);
715 | c.type = get32(s);
716 | return c;
717 | }
718 |
719 | static int check_png_header(stbi *s)
720 | {
721 | static immutable ubyte[8] png_sig = [ 137, 80, 78, 71, 13, 10, 26, 10 ];
722 | for (int i = 0; i < 8; ++i)
723 | {
724 | ubyte headerByte = get8u(s);
725 | ubyte expected = png_sig[i];
726 | if (headerByte != expected)
727 | assert(false, "Bad PNG sig, not a PNG");
728 | }
729 | return 1;
730 | }
731 |
732 | struct png
733 | {
734 | stbi *s;
735 | ubyte *idata;
736 | ubyte *expanded;
737 | ubyte *out_;
738 | }
739 |
740 |
741 | enum : int
742 | {
743 | F_none=0, F_sub=1, F_up=2, F_avg=3, F_paeth=4,
744 | F_avg_first, F_paeth_first
745 | }
746 |
747 | static immutable ubyte[5] first_row_filter =
748 | [
749 | F_none, F_sub, F_none, F_avg_first, F_paeth_first
750 | ];
751 |
752 | static int paeth(int a, int b, int c)
753 | {
754 | int p = a + b - c;
755 | int pa = abs(p-a);
756 | int pb = abs(p-b);
757 | int pc = abs(p-c);
758 | if (pa <= pb && pa <= pc) return a;
759 | if (pb <= pc) return b;
760 | return c;
761 | }
762 |
763 | // create the png data from post-deflated data
764 | static int create_png_image_raw(png *a, ubyte *raw, uint raw_len, int out_n, uint x, uint y)
765 | {
766 | stbi *s = a.s;
767 | uint i,j,stride = x*out_n;
768 | int k;
769 | int img_n = s.img_n; // copy it into a local for later
770 | assert(out_n == s.img_n || out_n == s.img_n+1);
771 | if (stbi_png_partial) y = 1;
772 | a.out_ = cast(ubyte*) malloc(x * y * out_n);
773 | if (!a.out_) assert(false, "Out of memory");
774 | if (!stbi_png_partial) {
775 | if (s.img_x == x && s.img_y == y) {
776 | if (raw_len != (img_n * x + 1) * y) assert(false, "Not enough pixels, corrupt PNG");
777 | } else { // interlaced:
778 | if (raw_len < (img_n * x + 1) * y) assert(false, "Not enough pixels, corrupt PNG");
779 | }
780 | }
781 | for (j=0; j < y; ++j) {
782 | ubyte *cur = a.out_ + stride*j;
783 | ubyte *prior = cur - stride;
784 | int filter = *raw++;
785 | if (filter > 4) assert(false, "Invalid filter, corrupt PNG");
786 | // if first row, use special filter that doesn't sample previous row
787 | if (j == 0) filter = first_row_filter[filter];
788 | // handle first pixel explicitly
789 | for (k=0; k < img_n; ++k) {
790 | switch (filter) {
791 | case F_none : cur[k] = raw[k]; break;
792 | case F_sub : cur[k] = raw[k]; break;
793 | case F_up : cur[k] = cast(ubyte)(raw[k] + prior[k]); break;
794 | case F_avg : cur[k] = cast(ubyte)(raw[k] + (prior[k]>>1)); break;
795 | case F_paeth : cur[k] = cast(ubyte) (raw[k] + paeth(0,prior[k],0)); break;
796 | case F_avg_first : cur[k] = raw[k]; break;
797 | case F_paeth_first: cur[k] = raw[k]; break;
798 | default: break;
799 | }
800 | }
801 | if (img_n != out_n) cur[img_n] = 255;
802 | raw += img_n;
803 | cur += out_n;
804 | prior += out_n;
805 | // this is a little gross, so that we don't switch per-pixel or per-component
806 | if (img_n == out_n) {
807 |
808 | for (i=x-1; i >= 1; --i, raw+=img_n,cur+=img_n,prior+=img_n)
809 | for (k=0; k < img_n; ++k)
810 | {
811 | switch (filter) {
812 | case F_none: cur[k] = raw[k]; break;
813 | case F_sub: cur[k] = cast(ubyte)(raw[k] + cur[k-img_n]); break;
814 | case F_up: cur[k] = cast(ubyte)(raw[k] + prior[k]); break;
815 | case F_avg: cur[k] = cast(ubyte)(raw[k] + ((prior[k] + cur[k-img_n])>>1)); break;
816 | case F_paeth: cur[k] = cast(ubyte) (raw[k] + paeth(cur[k-img_n],prior[k],prior[k-img_n])); break;
817 | case F_avg_first: cur[k] = cast(ubyte)(raw[k] + (cur[k-img_n] >> 1)); break;
818 | case F_paeth_first: cur[k] = cast(ubyte) (raw[k] + paeth(cur[k-img_n],0,0)); break;
819 | default: break;
820 | }
821 | }
822 | } else {
823 | assert(img_n+1 == out_n);
824 |
825 | for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n)
826 | for (k=0; k < img_n; ++k)
827 | {
828 | switch (filter) {
829 | case F_none: cur[k] = raw[k]; break;
830 | case F_sub: cur[k] = cast(ubyte)(raw[k] + cur[k-out_n]); break;
831 | case F_up: cur[k] = cast(ubyte)(raw[k] + prior[k]); break;
832 | case F_avg: cur[k] = cast(ubyte)(raw[k] + ((prior[k] + cur[k-out_n])>>1)); break;
833 | case F_paeth: cur[k] = cast(ubyte) (raw[k] + paeth(cur[k-out_n],prior[k],prior[k-out_n])); break;
834 | case F_avg_first: cur[k] = cast(ubyte)(raw[k] + (cur[k-out_n] >> 1)); break;
835 | case F_paeth_first: cur[k] = cast(ubyte) (raw[k] + paeth(cur[k-out_n],0,0)); break;
836 | default: break;
837 | }
838 | }
839 | }
840 | }
841 | return 1;
842 | }
843 |
844 | int create_png_image(png *a, ubyte *raw, uint raw_len, int out_n, int interlaced)
845 | {
846 | ubyte *final_;
847 | int p;
848 | int save;
849 | if (!interlaced)
850 | return create_png_image_raw(a, raw, raw_len, out_n, a.s.img_x, a.s.img_y);
851 | save = stbi_png_partial;
852 | stbi_png_partial = 0;
853 |
854 | // de-interlacing
855 | final_ = cast(ubyte*) malloc(a.s.img_x * a.s.img_y * out_n);
856 | for (p=0; p < 7; ++p) {
857 | static immutable int[7] xorig = [ 0,4,0,2,0,1,0 ];
858 | static immutable int[7] yorig = [ 0,0,4,0,2,0,1 ];
859 | static immutable int[7] xspc = [ 8,8,4,4,2,2,1 ];
860 | static immutable int[7] yspc = [ 8,8,8,4,4,2,2 ];
861 | int i,j,x,y;
862 | // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1
863 | x = (a.s.img_x - xorig[p] + xspc[p]-1) / xspc[p];
864 | y = (a.s.img_y - yorig[p] + yspc[p]-1) / yspc[p];
865 | if (x && y) {
866 | if (!create_png_image_raw(a, raw, raw_len, out_n, x, y)) {
867 | free(final_);
868 | return 0;
869 | }
870 | for (j=0; j < y; ++j)
871 | for (i=0; i < x; ++i)
872 | memcpy(final_ + (j*yspc[p]+yorig[p])*a.s.img_x*out_n + (i*xspc[p]+xorig[p])*out_n,
873 | a.out_ + (j*x+i)*out_n, out_n);
874 | free(a.out_);
875 | raw += (x*out_n+1)*y;
876 | raw_len -= (x*out_n+1)*y;
877 | }
878 | }
879 | a.out_ = final_;
880 |
881 | stbi_png_partial = save;
882 | return 1;
883 | }
884 |
885 | static int compute_transparency(png *z, ubyte[3] tc, int out_n)
886 | {
887 | stbi *s = z.s;
888 | uint i, pixel_count = s.img_x * s.img_y;
889 | ubyte *p = z.out_;
890 |
891 | // compute color-based transparency, assuming we've
892 | // already got 255 as the alpha value in the output
893 | assert(out_n == 2 || out_n == 4);
894 |
895 | if (out_n == 2) {
896 | for (i=0; i < pixel_count; ++i) {
897 | p[1] = (p[0] == tc[0] ? 0 : 255);
898 | p += 2;
899 | }
900 | } else {
901 | for (i=0; i < pixel_count; ++i) {
902 | if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
903 | p[3] = 0;
904 | p += 4;
905 | }
906 | }
907 | return 1;
908 | }
909 |
910 | int expand_palette(png *a, ubyte *palette, int len, int pal_img_n)
911 | {
912 | uint i, pixel_count = a.s.img_x * a.s.img_y;
913 | ubyte *p;
914 | ubyte *temp_out;
915 | ubyte *orig = a.out_;
916 |
917 | p = cast(ubyte*) malloc(pixel_count * pal_img_n);
918 | if (p == null)
919 | assert(false, "Out of memory");
920 |
921 | // between here and free(out) below, exitting would leak
922 | temp_out = p;
923 |
924 | if (pal_img_n == 3) {
925 | for (i=0; i < pixel_count; ++i) {
926 | int n = orig[i]*4;
927 | p[0] = palette[n ];
928 | p[1] = palette[n+1];
929 | p[2] = palette[n+2];
930 | p += 3;
931 | }
932 | } else {
933 | for (i=0; i < pixel_count; ++i) {
934 | int n = orig[i]*4;
935 | p[0] = palette[n ];
936 | p[1] = palette[n+1];
937 | p[2] = palette[n+2];
938 | p[3] = palette[n+3];
939 | p += 4;
940 | }
941 | }
942 | free(a.out_);
943 | a.out_ = temp_out;
944 |
945 | return 1;
946 | }
947 |
948 | int parse_png_file(png *z, int scan, int req_comp)
949 | {
950 | ubyte[1024] palette;
951 | ubyte pal_img_n=0;
952 | ubyte has_trans=0;
953 | ubyte[3] tc;
954 | uint ioff=0, idata_limit=0, i, pal_len=0;
955 | int first=1,k,interlace=0;
956 | stbi *s = z.s;
957 |
958 | z.expanded = null;
959 | z.idata = null;
960 | z.out_ = null;
961 |
962 | if (!check_png_header(s)) return 0;
963 |
964 | if (scan == SCAN_type) return 1;
965 |
966 | for (;;) {
967 | chunk c = get_chunk_header(s);
968 | switch (c.type) {
969 | case PNG_TYPE('I','H','D','R'): {
970 | int depth,color,comp,filter;
971 | if (!first) assert(false, "Multiple IHDR, corrupt PNG");
972 | first = 0;
973 | if (c.length != 13) assert(false, "Bad IHDR len, corrupt PNG");
974 | s.img_x = get32(s); if (s.img_x > (1 << 24)) assert(false, "Very large image (corrupt?)");
975 | s.img_y = get32(s); if (s.img_y > (1 << 24)) assert(false, "Very large image (corrupt?)");
976 | depth = get8(s); if (depth != 8) assert(false, "8bit only, PNG not supported: 8-bit only");
977 | color = get8(s); if (color > 6) assert(false, "Bad ctype, corrupt PNG");
978 | if (color == 3) pal_img_n = 3; else if (color & 1) assert(false, "Bad ctype, corrupt PNG");
979 | comp = get8(s); if (comp) assert(false, "Bad comp method, corrupt PNG");
980 | filter= get8(s); if (filter) assert(false, "Bad filter method, corrupt PNG");
981 | interlace = get8(s); if (interlace>1) assert(false, "Bad interlace method, corrupt PNG");
982 | if (!s.img_x || !s.img_y) assert(false, "0-pixel image, corrupt PNG");
983 | if (!pal_img_n) {
984 | s.img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);
985 | if ((1 << 30) / s.img_x / s.img_n < s.img_y) assert(false, "Image too large to decode");
986 | if (scan == SCAN_header) return 1;
987 | } else {
988 | // if paletted, then pal_n is our final components, and
989 | // img_n is # components to decompress/filter.
990 | s.img_n = 1;
991 | if ((1 << 30) / s.img_x / 4 < s.img_y) assert(false, "Too large, corrupt PNG");
992 | // if SCAN_header, have to scan to see if we have a tRNS
993 | }
994 | break;
995 | }
996 |
997 | case PNG_TYPE('P','L','T','E'): {
998 | if (first) assert(false, "first not IHDR, corrupt PNG");
999 | if (c.length > 256*3) assert(false, "invalid PLTE, corrupt PNG");
1000 | pal_len = c.length / 3;
1001 | if (pal_len * 3 != c.length) assert(false, "invalid PLTE, corrupt PNG");
1002 | for (i=0; i < pal_len; ++i) {
1003 | palette[i*4+0] = get8u(s);
1004 | palette[i*4+1] = get8u(s);
1005 | palette[i*4+2] = get8u(s);
1006 | palette[i*4+3] = 255;
1007 | }
1008 | break;
1009 | }
1010 |
1011 | case PNG_TYPE('t','R','N','S'): {
1012 | if (first) assert(false, "first not IHDR, cCorrupt PNG");
1013 | if (z.idata) assert(false, "tRNS after IDAT, corrupt PNG");
1014 | if (pal_img_n) {
1015 | if (scan == SCAN_header) { s.img_n = 4; return 1; }
1016 | if (pal_len == 0) assert(false, "tRNS before PLTE, corrupt PNG");
1017 | if (c.length > pal_len) assert(false, "bad tRNS len, corrupt PNG");
1018 | pal_img_n = 4;
1019 | for (i=0; i < c.length; ++i)
1020 | palette[i*4+3] = get8u(s);
1021 | } else {
1022 | if (!(s.img_n & 1)) assert(false, "tRNS with alpha, corrupt PNG");
1023 | if (c.length != cast(uint) s.img_n*2) assert(false, "bad tRNS len, corrupt PNG");
1024 | has_trans = 1;
1025 | for (k=0; k < s.img_n; ++k)
1026 | tc[k] = cast(ubyte) get16(s); // non 8-bit images will be larger
1027 | }
1028 | break;
1029 | }
1030 |
1031 | case PNG_TYPE('I','D','A','T'): {
1032 | if (first) assert(false, "first not IHDR, corrupt PNG");
1033 | if (pal_img_n && !pal_len) assert(false, "no PLTE, corrupt PNG");
1034 | if (scan == SCAN_header) { s.img_n = pal_img_n; return 1; }
1035 | if (ioff + c.length > idata_limit) {
1036 | ubyte *p;
1037 | if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;
1038 | while (ioff + c.length > idata_limit)
1039 | idata_limit *= 2;
1040 | p = cast(ubyte*) realloc(z.idata, idata_limit); if (p == null) assert(false, "outofmem, cOut of memory");
1041 | z.idata = p;
1042 | }
1043 | if (!getn(s, z.idata+ioff,c.length)) assert(false, "outofdata, corrupt PNG");
1044 | ioff += c.length;
1045 | break;
1046 | }
1047 |
1048 | case PNG_TYPE('I','E','N','D'): {
1049 | uint raw_len;
1050 | if (first) assert(false, "first not IHDR, corrupt PNG");
1051 | if (scan != SCAN_load) return 1;
1052 | if (z.idata == null) assert(false, "no IDAT, corrupt PNG");
1053 | z.expanded = stbi_zlib_decode_malloc_guesssize_headerflag(z.idata, ioff, 16384, cast(int *) &raw_len, 1);
1054 | if (z.expanded == null) return 0; // zlib should set error
1055 | free(z.idata); z.idata = null;
1056 | if ((req_comp == s.img_n+1 && req_comp != 3 && !pal_img_n) || has_trans)
1057 | s.img_out_n = s.img_n+1;
1058 | else
1059 | s.img_out_n = s.img_n;
1060 | if (!create_png_image(z, z.expanded, raw_len, s.img_out_n, interlace)) return 0;
1061 | if (has_trans)
1062 | if (!compute_transparency(z, tc, s.img_out_n)) return 0;
1063 | if (pal_img_n) {
1064 | // pal_img_n == 3 or 4
1065 | s.img_n = pal_img_n; // record the actual colors we had
1066 | s.img_out_n = pal_img_n;
1067 | if (req_comp >= 3) s.img_out_n = req_comp;
1068 | if (!expand_palette(z, palette.ptr, pal_len, s.img_out_n))
1069 | return 0;
1070 | }
1071 | free(z.expanded); z.expanded = null;
1072 | return 1;
1073 | }
1074 |
1075 | default:
1076 | // if critical, fail
1077 | if (first) assert(false, "first not IHDR, corrupt PNG");
1078 | if ((c.type & (1 << 29)) == 0) {
1079 | assert(false, "PNG not supported: unknown chunk type");
1080 | }
1081 | skip(s, c.length);
1082 | break;
1083 | }
1084 | // end of chunk, read and skip CRC
1085 | get32(s);
1086 | }
1087 | }
1088 |
1089 | ubyte *do_png(png *p, int *x, int *y, int *n, int req_comp)
1090 | {
1091 | ubyte *result=null;
1092 | if (req_comp < 0 || req_comp > 4)
1093 | assert(false, "Internal error: bad req_comp");
1094 | if (parse_png_file(p, SCAN_load, req_comp)) {
1095 | result = p.out_;
1096 | p.out_ = null;
1097 | if (req_comp && req_comp != p.s.img_out_n) {
1098 | result = convert_format(result, p.s.img_out_n, req_comp, p.s.img_x, p.s.img_y);
1099 | p.s.img_out_n = req_comp;
1100 | if (result == null) return result;
1101 | }
1102 | *x = p.s.img_x;
1103 | *y = p.s.img_y;
1104 | if (n) *n = p.s.img_n;
1105 | }
1106 | free(p.out_); p.out_ = null;
1107 | free(p.expanded); p.expanded = null;
1108 | free(p.idata); p.idata = null;
1109 |
1110 | return result;
1111 | }
1112 |
1113 | ubyte *stbi_png_load(stbi *s, int *x, int *y, int *comp, int req_comp)
1114 | {
1115 | png p;
1116 | p.s = s;
1117 | return do_png(&p, x,y,comp,req_comp);
1118 | }
1119 |
--------------------------------------------------------------------------------