Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -0,0 +1,44 @@
##########################################################################################
# VSTGUI Minesweeper Example Application
##########################################################################################
set(target Minesweeper)
set(${target}_sources
"source/commands.h"
"source/highscorelist.cpp"
"source/highscorelist.h"
"source/highscores.cpp"
"source/highscores.h"
"source/highscoreviewcontroller.cpp"
"source/highscoreviewcontroller.h"
"source/keepchildviewscentered.h"
"source/minefieldviewcontroller.cpp"
"source/minefieldviewcontroller.h"
"source/model.cpp"
"source/model.h"
"source/myapp.cpp"
"source/windowcontroller.cpp"
"source/windowcontroller.h"
)
set(Minesweeper_resources
"resource/Window.uidesc"
"resource/Minesweeper.icns"
)
set(Minesweeper_font
"resource/font/LCD14.otf"
"resource/font/NotoEmoji-Regular.ttf"
)
##########################################################################################
vstgui_add_executable(${target} "${${target}_sources}" )
vstgui_add_resources(${target} "${Minesweeper_resources}")
vstgui_add_resources(${target} "${Minesweeper_font}" "Fonts")
vstgui_set_target_bundle_id(${target} "vstgui.examples.minesweeper")
vstgui_set_target_infoplist(${target} "resource/Info.plist")
vstgui_set_target_rcfile(${target} "resource/Minesweeper.rc")
vstgui_set_cxx_version(${target} ${VSTGUI_CXX_VERSION})
target_include_directories(${target} PRIVATE ../../../../)
set_target_properties(${target} PROPERTIES ${APP_PROPERTIES} ${VSTGUI_STANDALONE_EXAMPLES_FOLDER})
Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Minesweeper</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleIconFile</key>
<string>Minesweeper.icns</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020 Arne Scheffler. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
@@ -0,0 +1 @@
APPL????
@@ -0,0 +1,665 @@
{
"vstgui-ui-description": {
"version": "1",
"variables": {},
"bitmaps": {},
"fonts": {
"control": {
"font-name": "Arial",
"size": "10"
},
"emoji": {
"font-name": "Noto Emoji",
"size": "12"
},
"highscore": {
"font-name": "Arial",
"size": "12"
},
"lcd": {
"alternative-font-names": "LCD 14",
"font-name": "LCD",
"size": "14"
},
"lost": {
"font-name": "Arial",
"size": "20"
},
"time": {
"font-name": "Arial",
"size": "6"
}
},
"colors": {
"background": "#d9d9d9ff",
"card.closed.back": "#a0a0a0ff",
"card.closed.frame": "#000000ff",
"card.flaged.back": "#0000001f",
"card.flaged.frame": "#888888b4",
"card.opened.back": "#ffffff00",
"card.opened.frame": "#888888b4",
"focus": "#40afe3ff",
"highscore.font": "#000000ff",
"label": "#000000ff",
"lcd.font": "#ff0000ff",
"lost.back": "#2c2c2c8c",
"lost.font": "#ff0000ff",
"textedit.font": "#ffffffff",
"textedit.frame": "#9b9b9bff"
},
"gradients": {
"Default TextButton Gradient": [
{
"rgba": "#dcdcdcff",
"start": "0"
},
{
"rgba": "#b4b4b4ff",
"start": "1"
}
],
"Default TextButton Gradient Highlighted": [
{
"rgba": "#b4b4b4ff",
"start": "0"
},
{
"rgba": "#646464ff",
"start": "1"
}
]
},
"control-tags": {
"CloseHighscoreView": "9",
"Cols": "1",
"Flags": "3",
"Mines": "2",
"MouseMode": "6",
"NewHighScoreName": "7",
"NewHighScoreOK": "8",
"Rows": "0",
"Start": "5",
"Time": "4"
},
"custom": {
"UIGridController": {
"Grids": "1x 1,5x 5,10x 10,12x 12,15x 15",
"Size": "5, 5"
},
"UITemplateController": {
"SelectedTemplate": "Window"
},
"UIEditController": {
"EditViewScale": "1",
"EditorSize": "0, 0, 1640, 1005",
"SplitViewSize_0_0": "0.8151174668028600134306316249421797692776",
"SplitViewSize_0_1": "0.1644535240040858048438110472488915547729",
"SplitViewSize_1_0": "0.567926455566905019622936379164457321167",
"SplitViewSize_1_1": "0.426966292134831448823462096697767265141",
"SplitViewSize_2_0": "0.8073170731707317582248606413486413657665",
"SplitViewSize_2_1": "0.1896341463414634165296490664331940934062",
"TabSwitchValue": "6",
"Version": "1"
},
"UIAttributesController": {
"UIAttributesController": "contr"
},
"UIViewCreatorDataSource": {
"SelectedRow": "30"
},
"UIDescFilePath": {
"path": "/Users/scheffle/Source/vstgui/vstgui/standalone/examples/minesweeper/resource/Window.uidesc"
},
"UITagsDataSource": {
"SelectedRow": "-1"
},
"UIColorsDataSource": {
"SelectedRow": "8"
},
"UIFontsDataSource": {
"SelectedRow": "2"
},
"UIGradientsDataSource": {
"SelectedRow": "-1"
},
"UIBitmapsDataSource": {
"SelectedRow": "-1"
},
"FocusDrawing": {
"color": "focus",
"enabled": "true",
"width": "2"
}
},
"templates": {
"Window": {
"attributes": {
"autosize": "left right top bottom ",
"background-color": "background",
"background-color-draw-style": "filled and stroked",
"class": "CViewContainer",
"minSize": "364, 394",
"mouse-enabled": "true",
"opacity": "1",
"origin": "0, 0",
"size": "364, 394",
"transparent": "false",
"wants-focus": "false"
},
"children": {
"CTextLabel": {
"attributes": {
"back-color": "~ BlackCColor",
"background-offset": "0, 0",
"class": "CTextLabel",
"default-value": "0.5",
"font": "control",
"font-antialias": "true",
"font-color": "label",
"frame-color": "~ BlackCColor",
"frame-width": "1",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "10, 10",
"round-rect-radius": "6",
"shadow-color": "~ RedCColor",
"size": "35, 20",
"style-3D-in": "false",
"style-3D-out": "false",
"style-no-draw": "false",
"style-no-frame": "false",
"style-no-text": "false",
"style-round-rect": "false",
"style-shadow-text": "false",
"text-alignment": "left",
"text-inset": "0, 0",
"text-rotation": "0",
"text-shadow-offset": "1, 1",
"title": "Mines :",
"transparent": "true",
"value-precision": "2",
"wants-focus": "false",
"wheel-inc-value": "0.1"
}
},
"CTextLabel": {
"attributes": {
"back-color": "label",
"background-offset": "0, 0",
"class": "CTextLabel",
"control-tag": "Flags",
"default-value": "0.5",
"font": "lcd",
"font-antialias": "true",
"font-color": "lcd.font",
"frame-color": "textedit.frame",
"frame-width": "-1",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "45, 9",
"round-rect-radius": "2",
"shadow-color": "~ RedCColor",
"size": "30, 22",
"style-3D-in": "false",
"style-3D-out": "false",
"style-no-draw": "false",
"style-no-frame": "false",
"style-no-text": "false",
"style-round-rect": "true",
"style-shadow-text": "false",
"text-alignment": "right",
"text-inset": "5, 0",
"text-rotation": "0",
"text-shadow-offset": "1, 1",
"title": "99",
"transparent": "false",
"value-precision": "2",
"wants-focus": "false",
"wheel-inc-value": "0.1"
}
},
"CTextButton": {
"attributes": {
"autosize": "right top ",
"class": "CTextButton",
"control-tag": "Start",
"default-value": "0.5",
"font": "control",
"frame-color": "label",
"frame-color-highlighted": "label",
"frame-width": "-1",
"gradient": "Default TextButton Gradient",
"gradient-highlighted": "Default TextButton Gradient Highlighted",
"icon-position": "left",
"icon-text-margin": "0",
"kick-style": "true",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "284, 11",
"round-radius": "3",
"size": "70, 18",
"text-alignment": "center",
"text-color": "label",
"text-color-highlighted": "textedit.font",
"title": "New Game",
"transparent": "false",
"wants-focus": "true",
"wheel-inc-value": "0.1"
}
},
"CViewContainer": {
"attributes": {
"autosize": "left right top bottom ",
"background-color": "label",
"background-color-draw-style": "stroked",
"class": "CViewContainer",
"custom-view-name": "MinefieldView",
"mouse-enabled": "true",
"opacity": "1",
"origin": "10, 40",
"size": "344, 344",
"sub-controller": "MinefieldController",
"transparent": "false",
"wants-focus": "false"
},
"children": {
"CViewContainer": {
"attributes": {
"autosize": "left right top bottom ",
"background-color": "lost.back",
"background-color-draw-style": "filled and stroked",
"class": "CViewContainer",
"custom-view-name": "LostView",
"mouse-enabled": "false",
"opacity": "0",
"origin": "2, 2",
"size": "338, 339",
"transparent": "false",
"wants-focus": "false"
},
"children": {
"CTextLabel": {
"attributes": {
"autosize": "left right top bottom ",
"back-color": "~ TransparentCColor",
"background-offset": "0, 0",
"class": "CTextLabel",
"default-value": "0.5",
"font": "lost",
"font-antialias": "true",
"font-color": "lost.font",
"frame-color": "~ TransparentCColor",
"frame-width": "1",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "false",
"opacity": "1",
"origin": "0, 0",
"round-rect-radius": "6",
"shadow-color": "label",
"size": "339, 339",
"style-3D-in": "false",
"style-3D-out": "false",
"style-no-draw": "false",
"style-no-frame": "false",
"style-no-text": "false",
"style-round-rect": "false",
"style-shadow-text": "true",
"text-alignment": "center",
"text-inset": "5, 0",
"text-rotation": "0",
"text-shadow-offset": "1, 1",
"title": "EXPLODED",
"transparent": "false",
"value-precision": "2",
"wants-focus": "false",
"wheel-inc-value": "0.1"
}
}
}
},
"CViewContainer": {
"attributes": {
"autosize": "left right top bottom ",
"background-color": "lost.back",
"background-color-draw-style": "filled and stroked",
"class": "CViewContainer",
"custom-view-name": "WonView",
"mouse-enabled": "false",
"opacity": "0",
"origin": "2, 2",
"size": "338, 339",
"transparent": "false",
"wants-focus": "false"
},
"children": {
"CTextLabel": {
"attributes": {
"autosize": "left right top bottom ",
"back-color": "lost.back",
"background-offset": "0, 0",
"class": "CTextLabel",
"default-value": "0.5",
"font": "lost",
"font-antialias": "true",
"font-color": "lost.font",
"frame-color": "~ TransparentCColor",
"frame-width": "1",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "0",
"origin": "0, 0",
"round-rect-radius": "6",
"shadow-color": "label",
"size": "338, 339",
"style-3D-in": "false",
"style-3D-out": "false",
"style-no-draw": "false",
"style-no-frame": "false",
"style-no-text": "false",
"style-round-rect": "false",
"style-shadow-text": "true",
"text-alignment": "center",
"text-inset": "5, 0",
"text-rotation": "0",
"text-shadow-offset": "1, 1",
"title": "FIELD CLEANED",
"transparent": "true",
"value-precision": "2",
"wants-focus": "false",
"wheel-inc-value": "0.1"
}
}
}
}
}
},
"CTextLabel": {
"attributes": {
"back-color": "label",
"background-offset": "0, 0",
"class": "CTextLabel",
"control-tag": "Time",
"default-value": "0.5",
"font": "lcd",
"font-antialias": "true",
"font-color": "lcd.font",
"frame-color": "textedit.frame",
"frame-width": "-1",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "110, 9",
"round-rect-radius": "2",
"shadow-color": "~ RedCColor",
"size": "40, 22",
"style-3D-in": "false",
"style-3D-out": "false",
"style-no-draw": "false",
"style-no-frame": "false",
"style-no-text": "false",
"style-round-rect": "true",
"style-shadow-text": "false",
"text-alignment": "right",
"text-inset": "5, 0",
"text-rotation": "0",
"text-shadow-offset": "1, 1",
"title": "000",
"transparent": "false",
"value-precision": "2",
"wants-focus": "false",
"wheel-inc-value": "0.1"
}
},
"CTextLabel": {
"attributes": {
"back-color": "~ BlackCColor",
"background-offset": "0, 0",
"class": "CTextLabel",
"default-value": "0.5",
"font": "control",
"font-antialias": "true",
"font-color": "label",
"frame-color": "~ BlackCColor",
"frame-width": "1",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "80, 10",
"round-rect-radius": "6",
"shadow-color": "~ RedCColor",
"size": "30, 20",
"style-3D-in": "false",
"style-3D-out": "false",
"style-no-draw": "false",
"style-no-frame": "false",
"style-no-text": "false",
"style-round-rect": "false",
"style-shadow-text": "false",
"text-alignment": "left",
"text-inset": "0, 0",
"text-rotation": "0",
"text-shadow-offset": "1, 1",
"title": "Time :",
"transparent": "true",
"value-precision": "2",
"wants-focus": "false",
"wheel-inc-value": "0.1"
}
},
"CViewContainer": {
"attributes": {
"autosize": "left right top bottom ",
"background-color": "lost.back",
"background-color-draw-style": "filled and stroked",
"class": "CViewContainer",
"custom-view-name": "MainView",
"mouse-enabled": "false",
"opacity": "0",
"origin": "0, 0",
"size": "365, 395",
"sub-controller": "NewHighScoreViewController",
"transparent": "false",
"wants-focus": "false"
},
"children": {
"CMultiLineTextLabel": {
"attributes": {
"auto-height": "false",
"back-color": "card.closed.frame",
"background-offset": "0, 0",
"class": "CMultiLineTextLabel",
"default-value": "0.5",
"font": "highscore",
"font-antialias": "true",
"font-color": "textedit.font",
"frame-color": "card.closed.frame",
"frame-width": "1",
"line-layout": "wrap",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "40, 80",
"round-rect-radius": "3",
"shadow-color": "lcd.font",
"size": "285, 56.3965",
"style-3D-in": "false",
"style-3D-out": "false",
"style-no-draw": "false",
"style-no-frame": "false",
"style-no-text": "false",
"style-round-rect": "true",
"style-shadow-text": "false",
"text-alignment": "center",
"text-inset": "0, 5",
"text-rotation": "0",
"text-shadow-offset": "1, 1",
"title": "You have a new HighScore!\\n\\nPlease enter your name:",
"transparent": "false",
"value-precision": "2",
"wants-focus": "false",
"wheel-inc-value": "0.1"
}
},
"CTextEdit": {
"attributes": {
"back-color": "card.closed.frame",
"background-offset": "0, 0",
"class": "CTextEdit",
"control-tag": "NewHighScoreName",
"default-value": "0.5",
"font": "highscore",
"font-antialias": "true",
"font-color": "textedit.font",
"frame-color": "card.closed.frame",
"frame-width": "1",
"immediate-text-change": "false",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "41, 143",
"placeholder-title": "Name",
"round-rect-radius": "3",
"secure-style": "false",
"shadow-color": "lcd.font",
"size": "285, 20",
"style-3D-in": "false",
"style-3D-out": "false",
"style-doubleclick": "false",
"style-no-draw": "false",
"style-no-frame": "false",
"style-no-text": "false",
"style-round-rect": "true",
"style-shadow-text": "false",
"text-alignment": "center",
"text-inset": "0, 0",
"text-rotation": "0",
"text-shadow-offset": "1, 1",
"transparent": "false",
"value-precision": "2",
"wants-focus": "true",
"wheel-inc-value": "0.1"
}
},
"CTextButton": {
"attributes": {
"class": "CTextButton",
"control-tag": "NewHighScoreOK",
"default-value": "0.5",
"font": "control",
"frame-color": "label",
"frame-color-highlighted": "label",
"frame-width": "-1",
"gradient": "Default TextButton Gradient",
"gradient-highlighted": "Default TextButton Gradient Highlighted",
"icon-position": "left",
"icon-text-margin": "0",
"kick-style": "false",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "false",
"opacity": "1",
"origin": "133, 176",
"round-radius": "3",
"size": "100, 18",
"text-alignment": "center",
"text-color": "label",
"text-color-highlighted": "textedit.font",
"title": "OK",
"transparent": "false",
"wants-focus": "true",
"wheel-inc-value": "0.1"
}
}
}
},
"CViewContainer": {
"attributes": {
"autosize": "left right top bottom ",
"background-color": "background",
"background-color-draw-style": "filled and stroked",
"class": "CViewContainer",
"mouse-enabled": "false",
"opacity": "0",
"origin": "15, 45",
"size": "335, 335",
"sub-controller": "HighScoreViewController",
"transparent": "false",
"wants-focus": "false"
},
"children": {
"CScrollView": {
"attributes": {
"auto-drag-scrolling": "false",
"auto-hide-scrollbars": "false",
"background-color": "~ BlackCColor",
"background-color-draw-style": "filled and stroked",
"bordered": "true",
"class": "CScrollView",
"container-size": "315, 259.091",
"custom-view-name": "DataBrowser",
"follow-focus-view": "false",
"horizontal-scrollbar": "false",
"mouse-enabled": "true",
"opacity": "1",
"origin": "10, 10",
"overlay-scrollbars": "false",
"scrollbar-background-color": "#ffffffc8",
"scrollbar-frame-color": "~ BlackCColor",
"scrollbar-scroller-color": "~ BlueCColor",
"scrollbar-width": "16",
"size": "315, 285",
"sub-controller": "DataBrowserController",
"transparent": "true",
"vertical-scrollbar": "false",
"wants-focus": "false"
}
},
"CTextButton": {
"attributes": {
"class": "CTextButton",
"control-tag": "CloseHighscoreView",
"default-value": "0.5",
"font": "control",
"frame-color": "label",
"frame-color-highlighted": "label",
"frame-width": "-1",
"gradient": "Default TextButton Gradient",
"gradient-highlighted": "Default TextButton Gradient Highlighted",
"icon-position": "left",
"icon-text-margin": "0",
"kick-style": "false",
"max-value": "1",
"min-value": "0",
"mouse-enabled": "true",
"opacity": "1",
"origin": "135, 305",
"round-radius": "3",
"size": "70, 18",
"text-alignment": "center",
"text-color": "label",
"text-color-highlighted": "textedit.font",
"title": "Close",
"transparent": "false",
"wants-focus": "true",
"wheel-inc-value": "0.1"
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,92 @@
Copyright (c) 2015 Fredrick Brennan (http://8type.net)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,34 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "vstgui/standalone/include/icommand.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
//------------------------------------------------------------------------
static constexpr IdStringPtr GameGroup = "Game";
static constexpr IdStringPtr ToggleHighscores = "Toggle Highscores";
static const Command NewGameCommand {GameGroup, "New Game"};
static const Command NewBeginnerGameCommand {GameGroup, "New Beginner Game"};
static const Command NewIntermediateGameCommand {GameGroup, "New Intermediate Game"};
static const Command NewExpertGameCommand {GameGroup, "New Expert Game"};
static const Command ToggleHighscoresCommand {GameGroup, ToggleHighscores};
//------------------------------------------------------------------------
static constexpr IdStringPtr MouseMode = "Use Mouse Mode";
static constexpr IdStringPtr TouchpadMode = "Use Touchpad Mode";
static const Command MouseModeCommand {GameGroup, MouseMode};
static const Command TouchpadModeCommand {GameGroup, TouchpadMode};
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,171 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "highscorelist.h"
#include "vstgui/uidescription/cstream.h"
#include <algorithm>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
namespace {
//------------------------------------------------------------------------
UTF8String readUntil (char separator, CFileStream& stream)
{
std::string str;
std::string::value_type character;
while (stream.readRaw (&character, sizeof (character)) == sizeof (character))
{
if (character == separator)
break;
str += character;
}
return {std::move (str)};
}
//------------------------------------------------------------------------
UTF8String replaceChars (const UTF8String& str, std::initializer_list<char> characters,
char replacement)
{
std::string result;
auto start = str.begin ();
auto current = start;
while (current != str.end ())
{
for (const auto& character : characters)
{
if (*current == character)
{
if (start != current)
result.append (start.base (), current.base ());
if (replacement)
result += replacement;
start = current;
++start;
break;
}
}
++current;
}
if (start != current)
result.append (start.base (), current.base ());
return {std::move (result)};
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
Optional<HighScoreListModel> LoadHighScoreListModel (const UTF8String& path)
{
CFileStream stream;
if (!stream.open (path.data (), CFileStream::kReadMode))
return {};
HighScoreListModel list;
while (!stream.isEndOfFile ())
{
auto secondsStr = readUntil (':', stream);
if (auto seconds = UTF8StringView (secondsStr).toNumber<uint32_t> ())
{
auto name = readUntil (':', stream);
auto dateStr = readUntil ('\n', stream);
if (auto date = UTF8StringView (dateStr).toNumber<uint32_t> ())
{
list.addHighscore (name, *seconds, *date);
}
}
else if (!stream.isEndOfFile ())
return {};
}
return {std::move (list)};
}
//------------------------------------------------------------------------
bool SaveHighScoreListModel (const HighScoreListModel& list, const UTF8String& path)
{
CFileStream stream;
if (!stream.open (path.data (), CFileStream::kWriteMode | CFileStream::kTruncateMode))
return false;
for (auto& element : list)
{
if (!element.valid ())
break;
stream << std::to_string (element.seconds);
stream << std::string (1, ':');
stream << element.name.getString ();
stream << std::string (1, ':');
stream << std::to_string (element.date);
stream << std::string (1, '\n');
}
return true;
}
//------------------------------------------------------------------------
void HighScoreListModel::clear ()
{
std::for_each (list.begin (), list.end (), [] (auto& sel) { sel = {}; });
}
//------------------------------------------------------------------------
auto HighScoreListModel::highscorePosition (uint32_t seconds) -> ListIterator
{
return std::find_if (list.begin (), list.end (),
[seconds] (const auto& entry) { return entry.seconds > seconds; });
}
//------------------------------------------------------------------------
auto HighScoreListModel::highscorePosition (uint32_t seconds) const -> ListConstIterator
{
return std::find_if (list.begin (), list.end (),
[seconds] (const auto& entry) { return entry.seconds > seconds; });
}
//------------------------------------------------------------------------
Optional<size_t> HighScoreListModel::isHighScore (uint32_t seconds) const
{
auto pos = highscorePosition (seconds);
if (pos == list.end ())
return {};
return {static_cast<size_t> (std::distance (list.begin (), pos))};
}
//------------------------------------------------------------------------
Optional<size_t> HighScoreListModel::addHighscore (UTF8StringPtr name, uint32_t seconds,
std::time_t date)
{
auto it = highscorePosition (seconds);
if (it == list.end ())
return {};
auto pos = std::distance (list.begin (), it);
auto end = list.begin ();
std::advance (end, list.size () - 1);
std::move_backward (it, end, list.end ());
list[pos].name = replaceChars (name, {':', '\n'}, '_');
list[pos].seconds = seconds;
list[pos].date = (date == 0) ? std::time (nullptr) : date;
return Optional<size_t> (pos);
}
//------------------------------------------------------------------------
auto HighScoreListModel::begin () const -> ListConstIterator
{
return list.begin ();
}
//------------------------------------------------------------------------
auto HighScoreListModel::end () const -> ListConstIterator
{
return list.end ();
}
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,98 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "vstgui/lib/cstring.h"
#include "vstgui/lib/optional.h"
#include <array>
#include <ctime>
#include <memory>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
//------------------------------------------------------------------------
class HighScoreListModel
{
public:
//------------------------------------------------------------------------
static constexpr size_t Size = 10u;
//------------------------------------------------------------------------
struct Entry
{
uint32_t seconds {std::numeric_limits<uint32_t>::max ()};
std::time_t date {};
UTF8String name;
bool valid () const { return seconds != std::numeric_limits<uint32_t>::max (); }
};
HighScoreListModel () { clear (); }
HighScoreListModel (HighScoreListModel&&) = default;
HighScoreListModel& operator= (HighScoreListModel&&) = default;
void clear ();
Optional<size_t> addHighscore (UTF8StringPtr name, uint32_t seconds, std::time_t date = 0);
Optional<size_t> isHighScore (uint32_t seconds) const;
using List = std::array<Entry, Size>;
using ListIterator = List::iterator;
using ListConstIterator = List::const_iterator;
ListConstIterator begin () const;
ListConstIterator end () const;
//------------------------------------------------------------------------
private:
ListIterator highscorePosition (uint32_t seconds);
ListConstIterator highscorePosition (uint32_t seconds) const;
List list;
};
//------------------------------------------------------------------------
Optional<HighScoreListModel> LoadHighScoreListModel (const UTF8String& path);
bool SaveHighScoreListModel (const HighScoreListModel& list, const UTF8String& path);
//------------------------------------------------------------------------
class HighScoreList
{
public:
static std::shared_ptr<HighScoreList> make (const UTF8String& path)
{
auto instance = std::make_shared<HighScoreList> ();
instance->path = path;
if (auto l = LoadHighScoreListModel (path))
{
instance->list = std::move (*l);
}
return instance;
}
const UTF8String& getPath () const { return path; }
const HighScoreListModel& get () const { return list; }
HighScoreListModel& get () { return list; }
Optional<size_t> addHighscore (UTF8StringPtr name, uint32_t seconds, std::time_t date = 0)
{
auto res = list.addHighscore (name, seconds, date);
if (res)
save ();
return res;
}
Optional<size_t> isHighScore (uint32_t seconds) const { return list.isHighScore (seconds); }
private:
void save () { SaveHighScoreListModel (list, path); }
UTF8String path;
HighScoreListModel list;
};
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,50 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "highscores.h"
#include "vstgui/standalone/include/iapplication.h"
#include "vstgui/standalone/include/icommondirectories.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
//------------------------------------------------------------------------
HighScores& HighScores::instance ()
{
static HighScores gInstance;
return gInstance;
}
//------------------------------------------------------------------------
std::shared_ptr<HighScoreList> HighScores::get (uint32_t rows, uint32_t cols, uint32_t mines)
{
auto path = IApplication::instance ().getCommonDirectories ().get (
CommonDirectoryLocation::AppPreferencesPath, {}, true);
if (!path)
return nullptr;
*path += getHighscoreListName (rows, cols, mines);
auto it = lists.find (path->getString ());
if (it != lists.end ())
{
return it->second;
}
auto list = HighScoreList::make (*path);
lists.insert ({path->getString (), list});
return list;
}
//------------------------------------------------------------------------
UTF8String HighScores::getHighscoreListName (uint32_t rows, uint32_t cols, uint32_t mines)
{
return toString (rows) + "x" + toString (cols) + "x" + toString (mines) + ".highscore";
}
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,31 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "highscorelist.h"
#include <unordered_map>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
//------------------------------------------------------------------------
class HighScores
{
public:
static HighScores& instance ();
static UTF8String getHighscoreListName (uint32_t rows, uint32_t cols, uint32_t mines);
std::shared_ptr<HighScoreList> get (uint32_t rows, uint32_t cols, uint32_t mines);
private:
std::unordered_map<std::string, std::shared_ptr<HighScoreList>> lists;
};
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,224 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "highscoreviewcontroller.h"
#include "keepchildviewscentered.h"
#include "vstgui/lib/cdatabrowser.h"
#include "vstgui/lib/cdrawcontext.h"
#include "vstgui/uidescription/iuidescription.h"
#include "vstgui/uidescription/uiattributes.h"
#include <cassert>
#include <ctime>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
//------------------------------------------------------------------------
HighScoreViewController::HighScoreViewController (IController* parent)
: DelegationController (parent)
{
columnWidths[0] = 0.06;
columnWidths[1] = 0.14;
columnWidths[2] = 0.5;
columnWidths[3] = 0.3;
font = kSystemFont;
}
//------------------------------------------------------------------------
void HighScoreViewController::setHighScoreList (const std::shared_ptr<HighScoreList>& l)
{
list = l;
if (dataBrowser)
dataBrowser->recalculateLayout ();
}
//------------------------------------------------------------------------
bool HighScoreViewController::isVisible () const
{
if (dataBrowser)
{
if (auto parent = dataBrowser->getParentView ())
return parent->isVisible ();
}
return false;
}
//------------------------------------------------------------------------
void HighScoreViewController::show ()
{
if (!dataBrowser)
return;
if (auto parent = dataBrowser->getParentView ())
{
parent->setMouseEnabled (true);
parent->setAlphaValue (1.f);
}
}
//------------------------------------------------------------------------
void HighScoreViewController::hide ()
{
if (!dataBrowser)
return;
if (auto parent = dataBrowser->getParentView ())
{
parent->setAlphaValue (0.f);
parent->setMouseEnabled (false);
}
}
//------------------------------------------------------------------------
void HighScoreViewController::dbAttached (CDataBrowser* browser)
{
dataBrowser = browser;
if (auto parent = browser->getParentView ())
{
assert (parent->asViewContainer ());
keepChildViewsCentered (parent->asViewContainer ());
}
}
//------------------------------------------------------------------------
void HighScoreViewController::dbRemoved (CDataBrowser* browser)
{
dataBrowser = nullptr;
}
//------------------------------------------------------------------------
int32_t HighScoreViewController::dbGetNumRows (CDataBrowser* browser)
{
return HighScoreListModel::Size;
}
//------------------------------------------------------------------------
int32_t HighScoreViewController::dbGetNumColumns (CDataBrowser* browser)
{
return NumCols;
};
//------------------------------------------------------------------------
CCoord HighScoreViewController::dbGetRowHeight (CDataBrowser* browser)
{
auto height = browser->getHeight () / (HighScoreListModel::Size + 1) - 1;
if (font->getSize () != height * 0.6)
font->setSize (height * 0.6);
return height;
}
//------------------------------------------------------------------------
CCoord HighScoreViewController::dbGetCurrentColumnWidth (int32_t index, CDataBrowser* browser)
{
if (index >= 0 && index < NumCols)
return browser->getWidth () * columnWidths[index];
return 10;
}
//------------------------------------------------------------------------
bool HighScoreViewController::dbGetLineWidthAndColor (CCoord& width, CColor& color,
CDataBrowser* browser)
{
width = 1.;
color = kBlackCColor;
return true;
}
//------------------------------------------------------------------------
void HighScoreViewController::dbDrawHeader (CDrawContext* context, const CRect& size,
int32_t column, int32_t flags, CDataBrowser* browser)
{
context->setFont (font);
context->setFontColor (fontColor);
UTF8String text;
switch (column)
{
case 1: text = "Sec"; break;
case 2: text = "Name"; break;
case 3: text = "Date"; break;
default: break;
}
if (!text.empty ())
{
context->drawString (text, size);
}
}
//------------------------------------------------------------------------
void HighScoreViewController::dbDrawCell (CDrawContext* context, const CRect& size, int32_t row,
int32_t column, int32_t flags, CDataBrowser* browser)
{
if (!list)
return;
auto entry = list->get ().begin ();
std::advance (entry, row);
if (entry == list->get ().end ())
return;
bool valid = entry->valid ();
context->setFont (font);
context->setFontColor (fontColor);
UTF8String text = "-";
CHoriTxtAlign align = kCenterText;
CRect r = size;
switch (column)
{
case 0:
{
text = toString (row + 1) + ".";
align = kRightText;
break;
}
case 1:
{
if (valid)
text = toString (entry->seconds);
break;
}
case 2:
{
if (valid)
{
text = entry->name;
align = kLeftText;
r.left += 5.;
}
break;
}
case 3:
{
if (valid)
{
char mbstr[100];
if (std::strftime (mbstr, sizeof (mbstr), "%F", std::localtime (&entry->date)))
{
text = mbstr;
}
}
break;
}
}
context->drawString (text, r, align);
}
//------------------------------------------------------------------------
CView* HighScoreViewController::createView (const UIAttributes& attributes,
const IUIDescription* description)
{
const auto attr = attributes.getAttributeValue (IUIDescription::kCustomViewName);
if (attr && *attr == "DataBrowser")
{
if (auto f = description->getFont ("highscore"))
font = f;
description->getColor ("highscore.font", fontColor);
return new CDataBrowser ({}, this, CDataBrowser::kDrawHeader | CDataBrowser::kDrawRowLines,
0.);
}
return nullptr;
}
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,59 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "highscorelist.h"
#include "vstgui/lib/ccolor.h"
#include "vstgui/lib/cfont.h"
#include "vstgui/lib/idatabrowserdelegate.h"
#include "vstgui/uidescription/delegationcontroller.h"
#include <array>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
//------------------------------------------------------------------------
class HighScoreViewController final : public DelegationController,
public DataBrowserDelegateAdapter,
public NonAtomicReferenceCounted
{
public:
HighScoreViewController (IController* parent);
void setHighScoreList (const std::shared_ptr<HighScoreList>& list);
void show ();
void hide ();
bool isVisible () const;
private:
int32_t dbGetNumRows (CDataBrowser* browser) override;
int32_t dbGetNumColumns (CDataBrowser* browser) override;
CCoord dbGetRowHeight (CDataBrowser* browser) override;
CCoord dbGetCurrentColumnWidth (int32_t index, CDataBrowser* browser) override;
void dbDrawHeader (CDrawContext* context, const CRect& size, int32_t column, int32_t flags,
CDataBrowser* browser) override;
void dbDrawCell (CDrawContext* context, const CRect& size, int32_t row, int32_t column,
int32_t flags, CDataBrowser* browser) override;
CView* createView (const UIAttributes& attributes, const IUIDescription* description) override;
void dbAttached (CDataBrowser* browser) override;
void dbRemoved (CDataBrowser* browser) override;
bool dbGetLineWidthAndColor (CCoord& width, CColor& color, CDataBrowser* browser) override;
static constexpr const size_t NumCols = 4;
SharedPointer<CFontDesc> font;
CColor fontColor {kBlackCColor};
CDataBrowser* dataBrowser {nullptr};
std::shared_ptr<HighScoreList> list;
std::array<CCoord, NumCols> columnWidths;
};
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,62 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "vstgui/lib/cviewcontainer.h"
#include "vstgui/lib/iviewlistener.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
//------------------------------------------------------------------------
inline void keepChildViewsCentered (CViewContainer* view)
{
//------------------------------------------------------------------------
class CenteredViewHandler : public ViewListenerAdapter
{
public:
CenteredViewHandler (CViewContainer* container) : parent (container)
{
parent->registerViewListener (this);
}
void viewSizeChanged (CView* view, const CRect& oldSize) override
{
if (!view->isAttached ())
return;
auto viewSize = view->getViewSize ();
auto diffX = viewSize.getWidth () - oldSize.getWidth ();
auto diffY = viewSize.getHeight () - oldSize.getHeight ();
if (diffX == 0. && diffY == 0.)
return;
diffX *= 0.5;
diffY *= 0.5;
parent->forEachChild ([&] (auto& child) {
auto vs = child->getViewSize ();
vs.offset (diffX, diffY);
child->setViewSize (vs);
child->setMouseableArea (vs);
});
}
private:
void viewWillDelete (CView* view) override
{
view->unregisterViewListener (this);
delete this;
}
CViewContainer* parent {nullptr};
};
new CenteredViewHandler (view);
}
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,430 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "minefieldviewcontroller.h"
#include "vstgui/lib/animation/animations.h"
#include "vstgui/lib/animation/timingfunctions.h"
#include "vstgui/lib/cdatabrowser.h"
#include "vstgui/lib/cdrawcontext.h"
#include "vstgui/lib/cvstguitimer.h"
#include "vstgui/standalone/include/helpers/value.h"
#include "vstgui/uidescription/iuidescription.h"
#include "vstgui/uidescription/uiattributes.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
MinefieldViewController::MinefieldViewController (IValue& flagsValue, IValue& timeValue,
IController* parent,
WonCallbackFunc&& wonCallback)
: DelegationController (parent)
, flagsValue (flagsValue)
, timeValue (timeValue)
, wonCallback (std::move (wonCallback))
{
}
//------------------------------------------------------------------------
void MinefieldViewController::startGame (uint32_t rows, uint32_t cols, uint32_t mines)
{
if (lostView)
{
lostView->removeAllAnimations ();
lostView->setAlphaValue (0.f);
}
if (wonView)
{
wonView->removeAllAnimations ();
wonView->setAlphaValue (0.f);
}
model = std::make_unique<Model> (rows, cols, mines, this);
numRows = rows;
numCols = cols;
updateCellSize (dataBrowser->getViewSize ().getSize ());
Value::performSinglePlainEdit (flagsValue, model->getNumberOfMines ());
Value::performSinglePlainEdit (timeValue, 0);
startTime = {};
}
//------------------------------------------------------------------------
void MinefieldViewController::setMouseMode (bool state)
{
mouseMode = state;
}
//------------------------------------------------------------------------
CView* MinefieldViewController::createView (const UIAttributes& attributes,
const IUIDescription* description)
{
const auto attr = attributes.getAttributeValue (IUIDescription::kCustomViewName);
if (attr && *attr == "MinefieldView")
{
description->getColor ("card.closed.frame", closedFrameColor);
description->getColor ("card.closed.back", closedBackColor);
description->getColor ("card.opened.frame", openedFrameColor);
description->getColor ("card.opened.back", openedBackColor);
description->getColor ("card.flaged.frame", flagedFrameColor);
description->getColor ("card.flaged.back", flagedBackColor);
if (auto f = description->getFont ("emoji"))
emojiFont = *f;
smallEmojiFont = emojiFont;
if (dataBrowser)
dataBrowser->unregisterViewListener (this);
dataBrowser = nullptr;
dataBrowser = new CDataBrowser ({}, this, 0, 0.);
dataBrowser->registerViewListener (this);
return dataBrowser;
}
return DelegationController::createView (attributes, description);
}
//------------------------------------------------------------------------
CView* MinefieldViewController::verifyView (CView* view, const UIAttributes& attributes,
const IUIDescription* description)
{
const auto attr = attributes.getAttributeValue (IUIDescription::kCustomViewName);
if (attr)
{
if (*attr == "LostView")
{
lostView = view;
lostView->setAlphaValue (0.f);
}
else if (*attr == "WonView")
{
wonView = view;
wonView->setAlphaValue (0.f);
}
}
return DelegationController::verifyView (view, attributes, description);
}
//------------------------------------------------------------------------
void MinefieldViewController::onCellChanged (uint32_t row, uint32_t col)
{
if (!dataBrowser)
return;
auto r = dataBrowser->getCellBounds (CDataBrowser::Cell (row, col));
r.extend (1., 1.);
dataBrowser->invalidRect (r);
}
//------------------------------------------------------------------------
int32_t MinefieldViewController::dbGetNumRows (CDataBrowser* browser)
{
return numRows;
}
//------------------------------------------------------------------------
int32_t MinefieldViewController::dbGetNumColumns (CDataBrowser* browser)
{
return numCols;
}
//------------------------------------------------------------------------
CCoord MinefieldViewController::dbGetRowHeight (CDataBrowser* browser)
{
return cellSize.y;
}
//------------------------------------------------------------------------
CCoord MinefieldViewController::dbGetCurrentColumnWidth (int32_t index, CDataBrowser* browser)
{
return cellSize.x;
}
//------------------------------------------------------------------------
bool MinefieldViewController::dbGetLineWidthAndColor (CCoord& width, CColor& color,
CDataBrowser* browser)
{
width = 1;
color = kBlackCColor;
return true;
}
//------------------------------------------------------------------------
void MinefieldViewController::drawClosedCell (const CRect& r, CDrawContext& context) const
{
context.setFrameColor (closedFrameColor);
context.setFillColor (closedBackColor);
context.drawRect (r, kDrawFilledAndStroked);
}
//------------------------------------------------------------------------
void MinefieldViewController::drawOpenCell (const CRect& r, CDrawContext& context) const
{
context.setFrameColor (openedFrameColor);
context.setFillColor (openedBackColor);
context.drawRect (r, kDrawFilledAndStroked);
}
//------------------------------------------------------------------------
void MinefieldViewController::drawQuestionMark (const CRect& r, CDrawContext& context,
CFontRef f) const
{
context.setFont (f);
context.setFontColor (kRedCColor);
context.drawString (QuestionMarkCharacter, r);
}
//------------------------------------------------------------------------
void MinefieldViewController::drawQuestionMarkCell (const CRect& r, CDrawContext& context,
CFontRef f) const
{
context.setFrameColor (flagedFrameColor);
context.setFillColor (flagedBackColor);
context.drawRect (r, kDrawFilledAndStroked);
drawQuestionMark (r, context, f);
}
//------------------------------------------------------------------------
void MinefieldViewController::drawFlag (const CRect& r, CDrawContext& context, CFontRef f) const
{
context.setFont (f);
context.setFontColor (kRedCColor);
context.drawString (FlagCharacter, r);
}
//------------------------------------------------------------------------
void MinefieldViewController::drawFlaggedCell (const CRect& r, CDrawContext& context,
CFontRef f) const
{
context.setFrameColor (flagedFrameColor);
context.setFillColor (flagedBackColor);
context.drawRect (r, kDrawFilledAndStroked);
drawFlag (r, context, f);
}
//------------------------------------------------------------------------
void MinefieldViewController::drawMinedCell (const CRect& r, CDrawContext& context,
CFontRef f) const
{
context.setFont (f);
context.setFontColor (kBlackCColor);
context.drawString (BombCharacter, r);
}
//------------------------------------------------------------------------
void MinefieldViewController::drawExplosionCell (const CRect& r, CDrawContext& context,
CFontRef f) const
{
context.setFont (f);
context.setFontColor (kRedCColor);
context.drawString (ExplosionCharacter, r);
}
//------------------------------------------------------------------------
void MinefieldViewController::drawCellNeighbours (const CRect& r, CDrawContext& context, CFontRef f,
uint32_t neighbours)
{
if (neighbours == 0)
return;
auto valueStr = toString (neighbours);
context.setFont (f);
context.setFontColor (kBlackCColor);
context.drawString (valueStr, r);
}
//------------------------------------------------------------------------
void MinefieldViewController::dbDrawCell (CDrawContext* context, const CRect& size, int32_t row,
int32_t column, int32_t flags, CDataBrowser* browser)
{
if (row < 0 || column < 0 || !model)
return;
context->setDrawMode (kAntiAliasing);
context->setLineWidth (1.);
CRect r (size);
r.inset (1.5, 1.5);
if (!model->isDone () && !model->isTrapped () && !model->isOpen (row, column))
{
if (model->isFlag (row, column))
{
drawFlaggedCell (r, *context, &emojiFont);
}
else if (model->isQuestion (row, column))
{
drawQuestionMarkCell (r, *context, &emojiFont);
}
else
{
drawClosedCell (r, *context);
}
return;
}
drawOpenCell (r, *context);
if (model->isMine (row, column))
{
if (model->isTrapMine (row, column))
drawExplosionCell (r, *context, &emojiFont);
else
drawMinedCell (r, *context, &emojiFont);
}
else
{
auto value = model->getNumberOfMinesNearby (row, column);
drawCellNeighbours (r, *context, &font, value);
}
if (model->isFlag (row, column))
{
r.setWidth (r.getWidth () / 2.);
r.setHeight (r.getHeight () / 2.);
drawFlag (r, *context, &smallEmojiFont);
}
}
//------------------------------------------------------------------------
CMouseEventResult MinefieldViewController::dbOnMouseDown (const CPoint& where,
const CButtonState& buttons, int32_t row,
int32_t column, CDataBrowser* browser)
{
ignoreMouseUp = false;
if (!mouseMode && buttons.isLeftButton ())
{
mouseDownTimer = makeOwned<CVSTGUITimer> (
[this, row, column] (auto) {
mouseDownTimer = nullptr;
if (!model->isOpen (row, column))
{
model->mark (row, column);
checkGameOver ();
}
ignoreMouseUp = true;
},
60);
}
return kMouseEventHandled;
}
//------------------------------------------------------------------------
CMouseEventResult MinefieldViewController::dbOnMouseMoved (const CPoint& where,
const CButtonState& buttons, int32_t row,
int32_t column, CDataBrowser* browser)
{
return kMouseEventHandled;
}
//------------------------------------------------------------------------
CMouseEventResult MinefieldViewController::dbOnMouseUp (const CPoint& where,
const CButtonState& buttons, int32_t row,
int32_t column, CDataBrowser* browser)
{
mouseDownTimer = nullptr;
if (ignoreMouseUp || !model || row < 0 || column < 0 || model->isTrapped () || model->isDone ())
return kMouseEventHandled;
if (!model->isOpen (row, column))
{
if (buttons.isRightButton ())
{
model->mark (row, column);
}
else if (buttons.isLeftButton ())
{
if (!model->isFlag (row, column) && !model->isQuestion (row, column))
model->open (row, column);
else
model->mark (row, column);
}
checkGameOver ();
}
return kMouseEventHandled;
}
//------------------------------------------------------------------------
void MinefieldViewController::checkGameOver ()
{
if (startTime == TimePoint {})
{
startTime = Clock::now ();
gameTimer = makeOwned<CVSTGUITimer> ([this] (auto) { onTimer (); }, 1000);
}
Value::performSinglePlainEdit (flagsValue,
model->getNumberOfMines () - model->getNumberOfFlags ());
if (model->isDone () && !model->isTrapped ())
{
onGameWon ();
}
if (model->isTrapped ())
{
onGameLost ();
}
}
//------------------------------------------------------------------------
void MinefieldViewController::onGameLost ()
{
auto animView = lostView;
if (auto vc = lostView->asViewContainer ())
{
animView = vc->getView (0);
animView->setAlphaValue (0.f);
lostView->setAlphaValue (1.f);
}
animView->addAnimation ("Lost", new Animation::AlphaValueAnimation (1.f),
new Animation::RepeatTimingFunction (
new Animation::CubicBezierTimingFunction (
Animation::CubicBezierTimingFunction::easyInOut (250)),
-1));
gameTimer = nullptr;
dataBrowser->invalid ();
}
//------------------------------------------------------------------------
void MinefieldViewController::onGameWon ()
{
auto animView = wonView;
if (auto vc = wonView->asViewContainer ())
{
animView = vc->getView (0);
animView->setAlphaValue (0.f);
wonView->setAlphaValue (1.f);
}
animView->addAnimation ("Won", new Animation::AlphaValueAnimation (1.f),
new Animation::RepeatTimingFunction (
new Animation::CubicBezierTimingFunction (
Animation::CubicBezierTimingFunction::easyInOut (400)),
-1));
gameTimer = nullptr;
dataBrowser->invalid ();
if (wonCallback)
wonCallback (static_cast<int32_t> (Value::currentPlainValue (timeValue)));
}
//------------------------------------------------------------------------
void MinefieldViewController::onTimer ()
{
auto now = Clock::now ();
auto distance = std::chrono::duration_cast<std::chrono::seconds> (now - startTime).count ();
if (distance >= maxTimeInSeconds)
gameTimer = nullptr;
Value::performSinglePlainEdit (timeValue, static_cast<IValue::Type> (distance));
}
//------------------------------------------------------------------------
void MinefieldViewController::viewSizeChanged (CView* view, const CRect& oldSize)
{
if (!dataBrowser)
return;
auto newSize = view->getViewSize ().getSize ();
updateCellSize (newSize);
}
//------------------------------------------------------------------------
void MinefieldViewController::updateCellSize (CPoint newSize)
{
newSize -= {3., 3.};
cellSize.x = newSize.x / numCols;
cellSize.y = newSize.y / numRows;
dataBrowser->recalculateLayout ();
font.setSize (cellSize.y / 2.);
emojiFont.setSize (cellSize.y / 2.);
smallEmojiFont.setSize (font.getSize () / 2.);
}
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,112 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "model.h"
#include "vstgui/lib/ccolor.h"
#include "vstgui/lib/cfont.h"
#include "vstgui/lib/idatabrowserdelegate.h"
#include "vstgui/lib/iviewlistener.h"
#include "vstgui/standalone/include/ivalue.h"
#include "vstgui/uidescription/delegationcontroller.h"
#include <chrono>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
static constexpr auto maxTimeInSeconds = 999;
//------------------------------------------------------------------------
class MinefieldViewController : public DelegationController,
public DataBrowserDelegateAdapter,
public NonAtomicReferenceCounted,
public ViewListenerAdapter,
public Model::IListener
{
public:
static constexpr auto BombCharacter = "\xF0\x9F\x92\xA3";
static constexpr auto FlagCharacter = "\xF0\x9F\x9A\xA9";
static constexpr auto ExplosionCharacter = "\xF0\x9F\x92\xA5";
static constexpr auto QuestionMarkCharacter = "\xE2\x9D\x93";
using WonCallbackFunc = std::function<void (int32_t secondsToWin)>;
MinefieldViewController (IValue& flagsValue, IValue& timeValue, IController* parent,
WonCallbackFunc&& wonCallback);
void startGame (uint32_t rows, uint32_t cols, uint32_t mines);
void setMouseMode (bool state);
private:
CView* createView (const UIAttributes& attributes, const IUIDescription* description) override;
CView* verifyView (CView* view, const UIAttributes& attributes,
const IUIDescription* description) override;
void onCellChanged (uint32_t row, uint32_t col) override;
int32_t dbGetNumRows (CDataBrowser* browser) override;
int32_t dbGetNumColumns (CDataBrowser* browser) override;
CCoord dbGetRowHeight (CDataBrowser* browser) override;
CCoord dbGetCurrentColumnWidth (int32_t index, CDataBrowser* browser) override;
bool dbGetLineWidthAndColor (CCoord& width, CColor& color, CDataBrowser* browser) override;
void drawClosedCell (const CRect& r, CDrawContext& context) const;
void drawOpenCell (const CRect& r, CDrawContext& context) const;
void drawQuestionMark (const CRect& r, CDrawContext& context, CFontRef f) const;
void drawQuestionMarkCell (const CRect& r, CDrawContext& context, CFontRef f) const;
void drawFlag (const CRect& r, CDrawContext& context, CFontRef f) const;
void drawFlaggedCell (const CRect& r, CDrawContext& context, CFontRef f) const;
void drawMinedCell (const CRect& r, CDrawContext& context, CFontRef f) const;
void drawExplosionCell (const CRect& r, CDrawContext& context, CFontRef f) const;
void drawCellNeighbours (const CRect& r, CDrawContext& context, CFontRef f,
uint32_t neighbours);
void dbDrawCell (CDrawContext* context, const CRect& size, int32_t row, int32_t column,
int32_t flags, CDataBrowser* browser) override;
CMouseEventResult dbOnMouseDown (const CPoint& where, const CButtonState& buttons, int32_t row,
int32_t column, CDataBrowser* browser) override;
CMouseEventResult dbOnMouseMoved (const CPoint& where, const CButtonState& buttons, int32_t row,
int32_t column, CDataBrowser* browser) override;
CMouseEventResult dbOnMouseUp (const CPoint& where, const CButtonState& buttons, int32_t row,
int32_t column, CDataBrowser* browser) override;
void checkGameOver ();
void onGameLost ();
void onGameWon ();
void onTimer ();
void viewSizeChanged (CView* view, const CRect& oldSize) override;
void updateCellSize (CPoint newSize);
private:
SharedPointer<CDataBrowser> dataBrowser;
SharedPointer<CView> lostView;
SharedPointer<CView> wonView;
int32_t numRows {1};
int32_t numCols {1};
std::unique_ptr<Model> model;
CColor closedFrameColor {kBlackCColor};
CColor closedBackColor {kGreyCColor};
CColor openedFrameColor {kGreyCColor};
CColor openedBackColor {kTransparentCColor};
CColor flagedFrameColor {kGreyCColor};
CColor flagedBackColor {kTransparentCColor};
CPoint cellSize {30, 30};
CFontDesc font {*kSystemFont};
CFontDesc smallEmojiFont {*kSymbolFont};
CFontDesc emojiFont {*kSymbolFont};
IValue& flagsValue;
IValue& timeValue;
WonCallbackFunc wonCallback;
using Clock = std::chrono::steady_clock;
using TimePoint = std::chrono::time_point<Clock>;
TimePoint startTime;
SharedPointer<CVSTGUITimer> gameTimer;
SharedPointer<CVSTGUITimer> mouseDownTimer;
bool ignoreMouseUp {false};
bool mouseMode {true};
};
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,267 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "model.h"
#include <algorithm>
#include <random>
#include <cassert>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
//------------------------------------------------------------------------
Model::Model (uint32_t numberOfRows, uint32_t numberOfCols, uint32_t numberOfMines,
IListener* listener)
: mines (numberOfMines), listener (listener)
{
assert (numberOfMines < numberOfRows * numberOfCols);
allocateModel (numberOfRows, numberOfCols);
clearModel ();
setMines ();
calcNeighbours ();
}
//------------------------------------------------------------------------
uint32_t Model::getNumberOfMines () const
{
return mines;
}
//------------------------------------------------------------------------
uint32_t Model::getNumberOfFlags () const
{
return flagged;
}
//------------------------------------------------------------------------
bool Model::isTrapped () const
{
return trapped;
}
//------------------------------------------------------------------------
bool Model::isDone () const
{
if (flagged == mines)
{
for (auto row = 0u; row < matrix.size (); ++row)
{
for (auto col = 0u; col < matrix.front ().size (); ++col)
{
auto cell = matrix[row][col];
if (cell.isFlag () && !cell.isMine ())
return false;
}
}
return true;
}
return false;
}
//------------------------------------------------------------------------
void Model::open (uint32_t row, uint32_t col)
{
if (openInternal (row, col))
{
auto cell = matrix[row][col];
if (cell.isOpen () && cell.neighbours == 0)
{
openCleanNearby (row, col);
}
}
}
//------------------------------------------------------------------------
void Model::mark (uint32_t row, uint32_t col)
{
auto& cell = matrix[row][col];
if (cell.isFlag ())
{
--flagged;
cell.unsetFlag ();
cell.setQuestion ();
}
else if (cell.isQuestion ())
{
cell.unsetQuestion ();
}
else
{
++flagged;
cell.setFlag ();
}
if (listener)
listener->onCellChanged (row, col);
}
//------------------------------------------------------------------------
bool Model::isOpen (uint32_t row, uint32_t col) const
{
return matrix[row][col].isOpen ();
}
//------------------------------------------------------------------------
bool Model::isFlag (uint32_t row, uint32_t col) const
{
return matrix[row][col].isFlag ();
}
//------------------------------------------------------------------------
bool Model::isQuestion (uint32_t row, uint32_t col) const
{
return matrix[row][col].isQuestion ();
}
//------------------------------------------------------------------------
bool Model::isMine (uint32_t row, uint32_t col) const
{
return matrix[row][col].isMine ();
}
//------------------------------------------------------------------------
bool Model::isTrapMine (uint32_t row, uint32_t col) const
{
return matrix[row][col].isTrap ();
}
//------------------------------------------------------------------------
uint32_t Model::getNumberOfMinesNearby (uint32_t row, uint32_t col) const
{
return matrix[row][col].neighbours;
}
//------------------------------------------------------------------------
void Model::openCleanNearby (uint32_t row, uint32_t col)
{
const auto maxCol = matrix.front ().size () - 1;
const auto maxRow = matrix.size () - 1;
if (row > 0)
{
if (col > 0)
open (row - 1, col - 1);
open (row - 1, col);
if (col < maxCol)
open (row - 1, col + 1);
}
if (col > 0)
open (row, col - 1);
if (col < maxCol)
open (row, col + 1);
if (row < maxRow)
{
if (col > 0)
open (row + 1, col - 1);
open (row + 1, col);
if (col < maxCol)
open (row + 1, col + 1);
}
}
//------------------------------------------------------------------------
bool Model::openInternal (uint32_t row, uint32_t col)
{
auto& cell = matrix[row][col];
if (!cell.isOpen ())
{
cell.setOpen ();
++opened;
if (cell.isFlag ())
{
cell.unsetFlag ();
--flagged;
}
else if (cell.isQuestion ())
cell.unsetQuestion ();
if (listener)
listener->onCellChanged (row, col);
if (cell.isMine ())
{
cell.setTrap ();
trapped = true;
return false;
}
return true;
}
return false;
}
//------------------------------------------------------------------------
void Model::allocateModel (uint32_t numberOfRows, uint32_t numberOfCols)
{
matrix.resize (numberOfRows);
std::for_each (matrix.begin (), matrix.end (),
[numberOfCols] (auto& r) { r.resize (numberOfCols); });
}
//------------------------------------------------------------------------
void Model::clearModel ()
{
std::for_each (matrix.begin (), matrix.end (), [] (auto& r) {
std::for_each (r.begin (), r.end (), [] (auto& cell) { cell = {}; });
});
}
//------------------------------------------------------------------------
void Model::setMines ()
{
std::random_device rd;
std::mt19937 gen (rd ());
std::uniform_int_distribution<> rowDist (0, static_cast<int> (matrix.size () - 1));
std::uniform_int_distribution<> colDist (0, static_cast<int> (matrix.front ().size () - 1));
for (auto i = 0u; i < mines; ++i)
{
auto row = rowDist (gen);
auto col = colDist (gen);
if ((matrix[row][col]).isMine ())
{
assert (i > 0u);
--i;
continue;
}
matrix[row][col].setMine ();
}
}
//------------------------------------------------------------------------
void Model::calcNeighbours ()
{
const auto maxCol = matrix.front ().size () - 1;
const auto maxRow = matrix.size () - 1;
for (auto row = 0u; row < matrix.size (); ++row)
{
for (auto col = 0u; col < matrix.front ().size (); ++col)
{
auto& cell = matrix[row][col];
if (cell.isMine ())
continue;
if (row > 0)
{
if (col > 0)
cell.neighbours += matrix[row - 1][col - 1].isMine () ? 1 : 0;
cell.neighbours += matrix[row - 1][col].isMine () ? 1 : 0;
if (col < maxCol)
cell.neighbours += matrix[row - 1][col + 1].isMine () ? 1 : 0;
}
if (col > 0)
cell.neighbours += matrix[row][col - 1].isMine () ? 1 : 0;
if (col < maxCol)
cell.neighbours += matrix[row][col + 1].isMine () ? 1 : 0;
if (row < maxRow)
{
if (col > 0)
cell.neighbours += matrix[row + 1][col - 1].isMine () ? 1 : 0;
cell.neighbours += matrix[row + 1][col].isMine () ? 1 : 0;
if (col < maxCol)
cell.neighbours += matrix[row + 1][col + 1].isMine () ? 1 : 0;
}
}
}
}
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,98 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include <cstdint>
#include <vector>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
//------------------------------------------------------------------------
class Model
{
private:
enum class Bit : uint16_t
{
Mine = 1 << 0,
Open = 1 << 1,
Flag = 1 << 2,
Trap = 1 << 3,
Question = 1 << 4,
};
static bool hasBit (uint16_t s, Bit flag) { return (s & static_cast<uint16_t> (flag)) != 0; }
static void setBit (uint16_t& s, Bit flag) { s |= static_cast<uint16_t> (flag); }
static void unsetBit (uint16_t& s, Bit flag) { s &= ~static_cast<uint16_t> (flag); }
struct Cell
{
uint16_t neighbours {0};
uint16_t flags {0};
bool isMine () const { return hasBit (flags, Bit::Mine); }
bool isOpen () const { return hasBit (flags, Bit::Open); }
bool isFlag () const { return hasBit (flags, Bit::Flag); }
bool isTrap () const { return hasBit (flags, Bit::Trap); }
bool isQuestion () const { return hasBit (flags, Bit::Question); }
void setMine () { setBit (flags, Bit::Mine); }
void setOpen () { setBit (flags, Bit::Open); }
void setTrap () { setBit (flags, Bit::Trap); }
void setFlag () { setBit (flags, Bit::Flag); }
void unsetFlag () { unsetBit (flags, Bit::Flag); }
void setQuestion () { setBit (flags, Bit::Question); }
void unsetQuestion () { unsetBit (flags, Bit::Question); }
};
using Row = std::vector<Cell>;
using Matrix = std::vector<Row>;
public:
struct IListener
{
virtual void onCellChanged (uint32_t row, uint32_t col) = 0;
};
Model (uint32_t numberOfRows, uint32_t numberOfCols, uint32_t numberOfMines,
IListener* listener);
uint32_t getNumberOfMines () const;
uint32_t getNumberOfFlags () const;
void open (uint32_t row, uint32_t col);
void mark (uint32_t row, uint32_t col);
bool isTrapped () const;
bool isDone () const;
bool isOpen (uint32_t row, uint32_t col) const;
bool isFlag (uint32_t row, uint32_t col) const;
bool isQuestion (uint32_t row, uint32_t col) const;
bool isMine (uint32_t row, uint32_t col) const;
bool isTrapMine (uint32_t row, uint32_t col) const;
uint32_t getNumberOfMinesNearby (uint32_t row, uint32_t col) const;
private:
void openCleanNearby (uint32_t row, uint32_t col);
bool openInternal (uint32_t row, uint32_t col);
void allocateModel (uint32_t numberOfRows, uint32_t numberOfCols);
void clearModel ();
void setMines ();
void calcNeighbours ();
Matrix matrix;
uint32_t mines {0};
uint32_t opened {0};
uint32_t flagged {0};
bool trapped {false};
IListener* listener {nullptr};
};
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,86 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "commands.h"
#include "windowcontroller.h"
#include "vstgui/standalone/include/helpers/appdelegate.h"
#include "vstgui/standalone/include/helpers/menubuilder.h"
#include "vstgui/standalone/include/helpers/windowlistener.h"
#include "vstgui/standalone/include/iapplication.h"
#include "vstgui/standalone/include/iuidescwindow.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
//------------------------------------------------------------------------
class MyApplication final : public Application::DelegateAdapter,
public WindowListenerAdapter,
public MenuBuilderAdapter
{
public:
MyApplication ()
: Application::DelegateAdapter ({"Minesweeper", "1.0.0", VSTGUI_STANDALONE_APP_URI})
{
}
void finishLaunching () override
{
auto windowController = std::make_shared<WindowController> ();
UIDesc::Config config;
config.uiDescFileName = "Window.uidesc";
config.viewName = "Window";
config.customization = windowController;
config.modelBinding = windowController;
config.windowConfig.title = "Minesweeper";
config.windowConfig.autoSaveFrameName = "MinesweeperWindow";
config.windowConfig.style.border ().close ().centered ().size ();
if (auto window = UIDesc::makeWindow (config))
{
window->show ();
window->registerWindowListener (this);
}
else
{
IApplication::instance ().quit ();
}
}
void onClosed (const IWindow& window) override { IApplication::instance ().quit (); }
SortFunction getCommandGroupSortFunction (const Interface& context,
const UTF8String& group) const override
{
if (group == GameGroup)
{
return [] (const UTF8String& lhs, const UTF8String& rhs) {
static const auto order = {NewGameCommand.name,
NewBeginnerGameCommand.name,
NewIntermediateGameCommand.name,
NewExpertGameCommand.name,
MouseModeCommand.name,
TouchpadModeCommand.name,
ToggleHighscoresCommand.name};
auto leftIndex = std::find (order.begin (), order.end (), lhs);
auto rightIndex = std::find (order.begin (), order.end (), rhs);
return std::distance (leftIndex, rightIndex) > 0;
};
}
return {};
}
bool prependMenuSeparator (const Interface& context, const Command& cmd) const override
{
if (cmd == Commands::CloseWindow || cmd == MouseModeCommand || cmd == ToggleHighscoresCommand)
return true;
return false;
}
};
static Application::Init gAppDelegate (std::make_unique<MyApplication> ());
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,467 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "commands.h"
#include "highscores.h"
#include "highscoreviewcontroller.h"
#include "keepchildviewscentered.h"
#include "minefieldviewcontroller.h"
#include "vstgui/standalone/include/helpers/preferences.h"
#include "vstgui/standalone/include/helpers/value.h"
#include "vstgui/standalone/include/iapplication.h"
#include "vstgui/uidescription/delegationcontroller.h"
#include "vstgui/uidescription/iuidescription.h"
#include "vstgui/uidescription/uiattributes.h"
#include "windowcontroller.h"
#include <cassert>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
//------------------------------------------------------------------------
class EnterHighScoreViewController final : public DelegationController,
public ValueListenerAdapter,
public NonAtomicReferenceCounted
{
public:
using OnEndEditFunc = std::function<void ()>;
EnterHighScoreViewController (IValue& nameValue, IValue& okValue, IController* parent,
OnEndEditFunc&& func)
: DelegationController (parent)
, nameValue (nameValue)
, okValue (okValue)
, onEndEditFunc (std::move (func))
{
okValue.registerListener (this);
okValue.setActive (false);
}
CView* verifyView (CView* view, const UIAttributes& attributes,
const IUIDescription* description) override
{
const auto attr = attributes.getAttributeValue (IUIDescription::kCustomViewName);
if (attr)
{
if (*attr == "MainView")
{
mainView = view;
mainView->setAlphaValue (0.f);
mainView->setMouseEnabled (false);
keepChildViewsCentered (mainView->asViewContainer ());
}
}
return DelegationController::verifyView (view, attributes, description);
}
void onNewHighscore (uint32_t secondsToWin, const std::shared_ptr<HighScoreList>& list)
{
currentSecondsToWin = secondsToWin;
highscoreList = list;
mainView->setAlphaValue (1.f);
mainView->setMouseEnabled (true);
okValue.setActive (true);
}
void onEndEdit (IValue& value) override
{
if (value.getValue () > 0.5)
{
value.performEdit (0.);
auto name = Value::currentStringValue (nameValue);
highscoreList->addHighscore (name, currentSecondsToWin);
highscoreList = nullptr;
mainView->setMouseEnabled (false);
mainView->setAlphaValue (0.f);
onEndEditFunc ();
}
}
private:
SharedPointer<CView> mainView;
IValue& nameValue;
IValue& okValue;
std::shared_ptr<HighScoreList> highscoreList;
uint32_t currentSecondsToWin {0};
OnEndEditFunc onEndEditFunc;
};
//------------------------------------------------------------------------
class DigitsDisplayConverter final : public IValueConverter
{
public:
DigitsDisplayConverter (ValueConverterPtr converter, uint32_t digits)
: converter (converter), digits (digits)
{
}
UTF8String valueAsString (IValue::Type value) const override
{
auto plain = static_cast<uint32_t> (std::round (converter->normalizedToPlain (value)));
UTF8String str;
auto val = static_cast<uint32_t> (std::pow (10, digits - 1));
for (auto i = digits; i > 0; --i)
{
auto v = plain / val;
str += toString (v);
plain -= v * val;
val /= 10;
}
return str;
}
IValue::Type stringAsValue (const UTF8String& string) const override
{
return converter->stringAsValue (string);
}
IValue::Type plainToNormalized (IValue::Type plain) const override
{
return converter->plainToNormalized (plain);
}
IValue::Type normalizedToPlain (IValue::Type normalized) const override
{
return converter->normalizedToPlain (normalized);
}
private:
ValueConverterPtr converter;
uint32_t digits {3};
};
//------------------------------------------------------------------------
static constexpr auto valueRows = "Rows";
static constexpr auto valueCols = "Cols";
static constexpr auto valueMines = "Mines";
static constexpr auto valueStart = "Start";
static constexpr auto valueFlags = "Flags";
static constexpr auto valueTime = "Time";
static constexpr auto valueMouseMode = "MouseMode";
static constexpr auto valueNewHighScoreOK = "NewHighScoreOK";
static constexpr auto valueNewHighScoreName = "NewHighScoreName";
static constexpr auto valueCloseHighscoreView = "CloseHighscoreView";
//------------------------------------------------------------------------
WindowController::WindowController ()
{
IApplication::instance ().registerCommand (NewGameCommand, 'n');
IApplication::instance ().registerCommand (NewBeginnerGameCommand, '1');
IApplication::instance ().registerCommand (NewIntermediateGameCommand, '2');
IApplication::instance ().registerCommand (NewExpertGameCommand, '3');
IApplication::instance ().registerCommand (MouseModeCommand, 0);
IApplication::instance ().registerCommand (TouchpadModeCommand, 0);
IApplication::instance ().registerCommand (ToggleHighscoresCommand, '/');
addCreateViewControllerFunc (
"MinefieldController", [this] (const auto& name, auto* parent, auto* uidesc) {
if (!minefieldViewController)
{
auto flagsValue = modelBinding.getValue (valueFlags);
auto timeValue = modelBinding.getValue (valueTime);
minefieldViewController = owned (new MinefieldViewController (
*flagsValue, *timeValue, parent,
[this] (uint32_t secondsToWin) { onWon (secondsToWin); }));
if (auto valueObject = modelBinding.getValue (valueMouseMode))
{
minefieldViewController->setMouseMode (valueObject->getValue () >= 0.5 ? true :
false);
}
}
minefieldViewController->remember ();
return minefieldViewController;
});
addCreateViewControllerFunc (
"NewHighScoreViewController", [this] (const auto& name, auto* parent, auto* uidesc) {
if (!enterHighscoreViewController)
{
auto nameValue = modelBinding.getValue (valueNewHighScoreName);
auto okValue = modelBinding.getValue (valueNewHighScoreOK);
enterHighscoreViewController = owned (new EnterHighScoreViewController (
*nameValue, *okValue, parent, [this] () { showHighscores (); }));
}
enterHighscoreViewController->remember ();
return enterHighscoreViewController;
});
addCreateViewControllerFunc (
"HighScoreViewController", [this] (const auto& name, auto* parent, auto* uidesc) {
if (!highscoreViewController)
highscoreViewController = owned (new HighScoreViewController (parent));
highscoreViewController->remember ();
return highscoreViewController;
});
modelBinding.addValue (
Value::make (valueRows, 0, Value::makeRangeConverter (8, 30, 0)),
UIDesc::ValueCalls::onEndEdit ([this] (auto& value) { verifyNumMines (); }));
modelBinding.addValue (
Value::make (valueCols, 0, Value::makeRangeConverter (8, 30, 0)),
UIDesc::ValueCalls::onEndEdit ([this] (auto& value) { verifyNumMines (); }));
modelBinding.addValue (
Value::make (valueMines, 0, Value::makeRangeConverter (4, 668, 0)),
UIDesc::ValueCalls::onEndEdit ([this] (auto& value) { verifyNumMines (); }));
modelBinding.addValue (Value::make (
valueFlags, 0,
std::make_shared<DigitsDisplayConverter> (Value::makeRangeConverter (0, 668, 0), 2)));
modelBinding.addValue (
Value::make (valueTime, 0,
std::make_shared<DigitsDisplayConverter> (
Value::makeRangeConverter (0, maxTimeInSeconds, 0), 3)));
if (auto value = modelBinding.getValue (valueMines))
Value::performSinglePlainEdit (*value.get (), 10);
modelBinding.addValue (
Value::make (valueStart),
UIDesc::ValueCalls::onEndEdit ([this] (auto& value) { startNewGame (); }));
modelBinding.addValue (
Value::make (valueMouseMode), UIDesc::ValueCalls::onPerformEdit ([this] (auto& value) {
if (minefieldViewController)
minefieldViewController->setMouseMode (value.getValue () >= 0.5 ? true : false);
}));
modelBinding.addValue (Value::makeStringValue (valueNewHighScoreName, ""));
modelBinding.addValue (Value::make (valueNewHighScoreOK));
modelBinding.addValue (Value::make (valueCloseHighscoreView),
UIDesc::ValueCalls::onPerformEdit ([this] (auto& value) {
if (value.getValue () >= 0.5)
{
value.performEdit (0.);
hideHighscores ();
}
}));
loadDefaults ();
}
//------------------------------------------------------------------------
WindowController::~WindowController () noexcept = default;
//------------------------------------------------------------------------
void WindowController::loadDefaults ()
{
Preferences prefs ("Values");
if (auto value = prefs.getNumber<int32_t> (valueRows))
{
if (auto valueObject = modelBinding.getValue (valueRows))
Value::performSinglePlainEdit (*valueObject, *value);
}
if (auto value = prefs.getNumber<int32_t> (valueCols))
{
if (auto valueObject = modelBinding.getValue (valueCols))
Value::performSinglePlainEdit (*valueObject, *value);
}
if (auto value = prefs.getNumber<int32_t> (valueMines))
{
if (auto valueObject = modelBinding.getValue (valueMines))
Value::performSinglePlainEdit (*valueObject, *value);
}
if (auto value = prefs.getNumber<bool> (valueMouseMode))
{
if (auto valueObject = modelBinding.getValue (valueMouseMode))
Value::performSinglePlainEdit (*valueObject, *value);
}
}
//------------------------------------------------------------------------
void WindowController::storeDefaults ()
{
Preferences prefs ("Values");
if (auto value = modelBinding.getValue (valueRows))
prefs.setNumber (valueRows, static_cast<int32_t> (Value::currentPlainValue (*value)));
if (auto value = modelBinding.getValue (valueCols))
prefs.setNumber (valueCols, static_cast<int32_t> (Value::currentPlainValue (*value)));
if (auto value = modelBinding.getValue (valueMines))
prefs.setNumber (valueMines, static_cast<int32_t> (Value::currentPlainValue (*value)));
if (auto value = modelBinding.getValue (valueMouseMode))
prefs.setNumber (valueMouseMode, static_cast<bool> (Value::currentPlainValue (*value)));
}
//------------------------------------------------------------------------
bool WindowController::canHandleCommand (const Command& command)
{
if (command.group == GameGroup)
{
if (command.name == TouchpadMode)
{
if (modelBinding.getValue (valueMouseMode)->getValue () < 0.5)
return false;
}
if (command.name == MouseMode)
{
if (modelBinding.getValue (valueMouseMode)->getValue () >= 0.5)
return false;
}
return true;
}
return false;
}
//------------------------------------------------------------------------
bool WindowController::handleCommand (const Command& command)
{
if (command.group != GameGroup)
return false;
if (command.name == ToggleHighscores)
{
showHideHighscores ();
return true;
}
if (command.name == TouchpadMode)
{
auto value = modelBinding.getValue (valueMouseMode);
Value::performSingleEdit (*value, 0.);
return true;
}
else if (command.name == MouseMode)
{
auto value = modelBinding.getValue (valueMouseMode);
Value::performSingleEdit (*value, 1.);
return true;
}
auto rowValue = modelBinding.getValue (valueRows);
auto colValue = modelBinding.getValue (valueCols);
auto mineValue = modelBinding.getValue (valueMines);
if (!rowValue || !colValue || !mineValue)
return false;
if (command == NewBeginnerGameCommand)
{
Value::performSinglePlainEdit (*rowValue, 9);
Value::performSinglePlainEdit (*colValue, 9);
Value::performSinglePlainEdit (*mineValue, 10);
}
else if (command == NewIntermediateGameCommand)
{
Value::performSinglePlainEdit (*rowValue, 16);
Value::performSinglePlainEdit (*colValue, 16);
Value::performSinglePlainEdit (*mineValue, 40);
}
else if (command == NewExpertGameCommand)
{
Value::performSinglePlainEdit (*rowValue, 16);
Value::performSinglePlainEdit (*colValue, 30);
Value::performSinglePlainEdit (*mineValue, 99);
}
else
{
hideHighscores ();
}
startNewGame ();
return true;
}
//------------------------------------------------------------------------
void WindowController::beforeShow (IWindow& w)
{
window = &w;
}
//------------------------------------------------------------------------
void WindowController::onShow (const IWindow& w)
{
startNewGame ();
}
//------------------------------------------------------------------------
void WindowController::onClosed (const IWindow& w)
{
storeDefaults ();
}
//------------------------------------------------------------------------
auto WindowController::getValues () const -> const ValueList&
{
return modelBinding.getValues ();
}
//------------------------------------------------------------------------
std::tuple<uint32_t, uint32_t, uint32_t> WindowController::getRowsColsMines ()
{
auto rows =
static_cast<uint32_t> (Value::currentPlainValue (*modelBinding.getValue (valueRows)));
auto cols =
static_cast<uint32_t> (Value::currentPlainValue (*modelBinding.getValue (valueCols)));
auto mines =
static_cast<uint32_t> (Value::currentPlainValue (*modelBinding.getValue (valueMines)));
return {rows, cols, mines};
}
//------------------------------------------------------------------------
void WindowController::startNewGame ()
{
assert (minefieldViewController);
uint32_t rows, cols, mines;
std::tie (rows, cols, mines) = getRowsColsMines ();
minefieldViewController->startGame (rows, cols, mines);
auto highscoreList = HighScores::instance ().get (rows, cols, mines);
if (!highscoreList)
return;
highscoreViewController->setHighScoreList (highscoreList);
}
//------------------------------------------------------------------------
void WindowController::verifyNumMines ()
{
uint32_t rows, cols, mines;
std::tie (rows, cols, mines) = getRowsColsMines ();
if (rows * cols < mines)
{
Value::performSinglePlainEdit (*modelBinding.getValue (valueMines), rows * cols * 0.8);
}
}
//------------------------------------------------------------------------
void WindowController::onWon (uint32_t secondsToWin)
{
uint32_t rows, cols, mines;
std::tie (rows, cols, mines) = getRowsColsMines ();
auto highscoreList = HighScores::instance ().get (rows, cols, mines);
if (!highscoreList)
return;
auto pos = highscoreList->isHighScore (secondsToWin);
if (pos)
{
enterHighscoreViewController->onNewHighscore (secondsToWin, highscoreList);
}
}
//------------------------------------------------------------------------
void WindowController::showHideHighscores ()
{
if (!highscoreViewController)
return;
if (highscoreViewController->isVisible ())
highscoreViewController->hide ();
else
showHighscores ();
}
//------------------------------------------------------------------------
void WindowController::showHighscores ()
{
assert (highscoreViewController);
uint32_t rows, cols, mines;
std::tie (rows, cols, mines) = getRowsColsMines ();
if (auto highscoreList = HighScores::instance ().get (rows, cols, mines))
{
highscoreViewController->setHighScoreList (highscoreList);
highscoreViewController->show ();
}
}
//------------------------------------------------------------------------
void WindowController::hideHighscores ()
{
assert (highscoreViewController);
highscoreViewController->hide ();
}
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI
@@ -0,0 +1,62 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "vstgui/standalone/include/helpers/uidesc/customization.h"
#include "vstgui/standalone/include/helpers/uidesc/modelbinding.h"
#include "vstgui/standalone/include/helpers/windowcontroller.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Minesweeper {
class MinefieldViewController;
class EnterHighScoreViewController;
class HighScoreViewController;
//------------------------------------------------------------------------
class WindowController final : public WindowControllerAdapter,
public UIDesc::Customization,
public UIDesc::IModelBinding,
public ICommandHandler
{
public:
WindowController ();
~WindowController () noexcept override;
void loadDefaults ();
void storeDefaults ();
bool canHandleCommand (const Command& command) override;
bool handleCommand (const Command& command) override;
void beforeShow (IWindow& w) override;
void onShow (const IWindow& w) override;
void onClosed (const IWindow& w) override;
const ValueList& getValues () const override;
std::tuple<uint32_t, uint32_t, uint32_t> getRowsColsMines ();
void startNewGame ();
void verifyNumMines ();
void onWon (uint32_t secondsToWin);
private:
void showHideHighscores ();
void showHighscores ();
void hideHighscores ();
UIDesc::ModelBindingCallbacks modelBinding;
SharedPointer<MinefieldViewController> minefieldViewController;
SharedPointer<EnterHighScoreViewController> enterHighscoreViewController;
SharedPointer<HighScoreViewController> highscoreViewController;
IWindow* window {nullptr};
};
//------------------------------------------------------------------------
} // Minesweeper
} // Standalone
} // VSTGUI