├── .gitignore ├── LICENSE.md ├── Lit.pro ├── README.md ├── main.qrc ├── platform └── mac │ ├── Info.plist │ ├── icon.icns │ └── lit.iconset │ ├── icon_128x128.png │ ├── icon_128x128@2x.png │ ├── icon_16x16.png │ ├── icon_16x16@2x.png │ ├── icon_256x256.png │ ├── icon_256x256@2x.png │ ├── icon_32x32.png │ ├── icon_32x32@2x.png │ ├── icon_512x512.png │ └── icon_512x512@2x.png ├── qml ├── DisplayWindow.qml ├── EditorWindow.qml └── main.qml └── src ├── LitNativeHandler.cpp ├── LitNativeHandler.h ├── LitNativeHandler_mac.mm └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.pro.user 2 | *.pro.user.* 3 | *.autosave 4 | build/ 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Poren Chiang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Lit.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | TARGET = Lit 3 | QT += qml quick 4 | 5 | # The .cpp file which was generated for your project. Feel free to hack it. 6 | SOURCES += \ 7 | src/main.cpp \ 8 | src/LitNativeHandler.cpp 9 | 10 | HEADERS += \ 11 | src/LitNativeHandler.h 12 | 13 | RESOURCES += \ 14 | main.qrc 15 | 16 | OTHER_FILES += qml/*.qml 17 | 18 | mac { 19 | SOURCES += \ 20 | src/LitNativeHandler_mac.mm 21 | 22 | QMAKE_INFO_PLIST = platform/mac/Info.plist 23 | ICON = platform/mac/icon.icns 24 | 25 | CONFIG(build_release) { 26 | # qmldir must match project folder name 27 | QMAKE_POST_LINK += macdeployqt Lit.app -qmldir=../lit-app/qml/ -verbose=1 -dmg; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lit 2 | === 3 | 4 | Lit is a Qt-based application aimed to make live text projecting like a breeze. 5 | 6 | Targets Qt 5.4 or higher. 7 | 8 | Supported syntaxes 9 | ------------------ 10 | ``` 11 | *italics* 12 | **bold text** 13 | `code segments` 14 | ~~strikethrough~~ 15 | ``` 16 | 17 | Functionalities 18 | --------------- 19 | 20 | `Cmd+L` toggles **Code Mode**, which will render text in monospace. 21 | 22 | 23 | Contributing 24 | ------------ 25 | 26 | Visit [Lit project page on GitHub](https://github.com/rschiang/lit-app). 27 | 28 | 29 | License 30 | ------- 31 | 32 | See [LICENSE](LICENSE.md). 33 | -------------------------------------------------------------------------------- /main.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qml/main.qml 4 | qml/EditorWindow.qml 5 | qml/DisplayWindow.qml 6 | 7 | 8 | -------------------------------------------------------------------------------- /platform/mac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | Lit 7 | CFBundleIconFile 8 | icon.icns 9 | CFBundleIdentifier 10 | tw.poren.lit 11 | CFBundleInfoDictionaryVersion 12 | 1.0 13 | CFBundleName 14 | Lit 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | 20 | 21 | -------------------------------------------------------------------------------- /platform/mac/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rschiang/lit-app/a1e0413ecd1e6c91db67265b7d0eb1d0bdf45a0a/platform/mac/icon.icns -------------------------------------------------------------------------------- /platform/mac/lit.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rschiang/lit-app/a1e0413ecd1e6c91db67265b7d0eb1d0bdf45a0a/platform/mac/lit.iconset/icon_128x128.png -------------------------------------------------------------------------------- /platform/mac/lit.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rschiang/lit-app/a1e0413ecd1e6c91db67265b7d0eb1d0bdf45a0a/platform/mac/lit.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /platform/mac/lit.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rschiang/lit-app/a1e0413ecd1e6c91db67265b7d0eb1d0bdf45a0a/platform/mac/lit.iconset/icon_16x16.png -------------------------------------------------------------------------------- /platform/mac/lit.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rschiang/lit-app/a1e0413ecd1e6c91db67265b7d0eb1d0bdf45a0a/platform/mac/lit.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /platform/mac/lit.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rschiang/lit-app/a1e0413ecd1e6c91db67265b7d0eb1d0bdf45a0a/platform/mac/lit.iconset/icon_256x256.png -------------------------------------------------------------------------------- /platform/mac/lit.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rschiang/lit-app/a1e0413ecd1e6c91db67265b7d0eb1d0bdf45a0a/platform/mac/lit.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /platform/mac/lit.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rschiang/lit-app/a1e0413ecd1e6c91db67265b7d0eb1d0bdf45a0a/platform/mac/lit.iconset/icon_32x32.png -------------------------------------------------------------------------------- /platform/mac/lit.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rschiang/lit-app/a1e0413ecd1e6c91db67265b7d0eb1d0bdf45a0a/platform/mac/lit.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /platform/mac/lit.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rschiang/lit-app/a1e0413ecd1e6c91db67265b7d0eb1d0bdf45a0a/platform/mac/lit.iconset/icon_512x512.png -------------------------------------------------------------------------------- /platform/mac/lit.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rschiang/lit-app/a1e0413ecd1e6c91db67265b7d0eb1d0bdf45a0a/platform/mac/lit.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /qml/DisplayWindow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 6.0 2 | 3 | Window { 4 | id: window 5 | title: "Lit" 6 | color: app.theme === "dark" ? "#1e1e1e" : "#fff" 7 | 8 | Text { 9 | id: label 10 | 11 | anchors { 12 | left: parent.left 13 | right: parent.right 14 | verticalCenter: parent.verticalCenter 15 | margins: 8 16 | } 17 | horizontalAlignment: app.mode === "code" ? TextEdit.AlignLeft : TextEdit.AlignHCenter 18 | 19 | lineHeight: 0.85 20 | wrapMode: Text.NoWrap 21 | textFormat: app.mode === "code" ? Text.PlainText : Text.RichText 22 | 23 | color: app.theme === "dark" ? "#f8f8f2" : "#222" 24 | 25 | 26 | font.family: app.mode === "code" ? "Source Code Pro, Menlo, Source Han Sans TC" : 27 | "Source Sans Pro, Proxima Nova, Source Han Sans TC" 28 | font.weight: Font.DemiBold 29 | renderType: TextEdit.NativeRendering 30 | smooth: true 31 | 32 | text: app.mode === "code" ? app.text : format(app.text) 33 | 34 | Behavior on font.pixelSize { 35 | SmoothedAnimation { 36 | duration: 50 37 | easing.type: Easing.InExpo 38 | } 39 | } 40 | 41 | function format(text) { 42 | let pageBreakIndex = text.lastIndexOf("\n\n") 43 | let pageText = pageBreakIndex >= 0 ? text.substr(pageBreakIndex+2) : text 44 | return pageText 45 | .replace(/\n+$/g, '\n') 46 | .replace(/&/g, "&") 47 | .replace(//g, ">") 49 | .replace(/`(.+)`/g, "$1") 50 | .replace(/\*\*([^\n\*]+)\*\*/g, "$1") 51 | .replace(/\*([^\s\n\*]+)\*/g, "*$1*") 52 | .replace(/~~(.+)~~/g, "$1") 53 | .replace(/\n/g, "
") 54 | } 55 | } 56 | 57 | Text { 58 | id: measurer 59 | opacity: 0 60 | renderType: Text.NativeRendering 61 | antialiasing: false 62 | 63 | width: label.width 64 | horizontalAlignment: label.horizontalAlignment 65 | lineHeight: label.lineHeight 66 | wrapMode: label.wrapMode 67 | textFormat: label.textFormat 68 | font.family: label.font.family 69 | font.weight: label.font.weight 70 | text: label.text 71 | } 72 | 73 | Timer { 74 | id: timer 75 | interval: 50 76 | repeat: false 77 | running: false 78 | triggeredOnStart: false 79 | 80 | onTriggered: { 81 | var baseSize = Math.min(window.width, window.height) 82 | var ratio = 1.125 83 | 84 | do { 85 | measurer.font.pixelSize = baseSize 86 | baseSize = Math.floor(baseSize / ratio) 87 | } while ((measurer.paintedWidth >= window.width || 88 | measurer.paintedHeight >= window.height) && baseSize >= 12) 89 | 90 | let size = measurer.font.pixelSize 91 | label.font.pixelSize = size 92 | label.font.letterSpacing = size * -0.025 93 | } 94 | } 95 | 96 | Connections { 97 | target: app 98 | 99 | function onTextChanged() { timer.start() } 100 | function onModeChanged() { timer.start() } 101 | } 102 | 103 | onWidthChanged: timer.start() 104 | onHeightChanged: timer.start() 105 | onScreenChanged: Native.fillScreen(window, window.screen) 106 | onWindowStateChanged: Native.makeWindowTitleBarTransparent(window) 107 | onVisibilityChanged: Native.makeWindowTitleBarTransparent(window) 108 | Component.onCompleted: { 109 | window.flags |= Qt.WindowDoesNotAcceptFocus 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /qml/EditorWindow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 6.0 2 | import QtQuick.Controls 6.0 3 | 4 | Window { 5 | id: window 6 | title: "Lit" 7 | 8 | width: 540 9 | height: 320 10 | color: app.theme === "dark" ? "#1e1e1e" : "#fff" 11 | 12 | Component.onCompleted: { 13 | window.flags |= Qt.WindowStaysOnTopHint 14 | } 15 | 16 | onClosing: Qt.quit() 17 | 18 | ScrollView { 19 | id: scrollView 20 | anchors.fill: parent 21 | 22 | TextArea { 23 | id: editor 24 | 25 | wrapMode: Text.Wrap 26 | textFormat: TextEdit.PlainText 27 | 28 | font.pixelSize: 24 29 | font.family: app.mode === "code" ? "Source Code Pro, Menlo, Source Han Sans TC" : 30 | "Source Sans Pro, Proxima Nova, Source Han Sans TC" 31 | font.weight: Font.Medium 32 | renderType: TextEdit.NativeRendering 33 | smooth: true 34 | 35 | focus: true 36 | selectByMouse: true 37 | 38 | onTextChanged: app.text = text 39 | 40 | Keys.onPressed: (event) => { 41 | if (event.modifiers & Qt.ControlModifier) 42 | switch (event.key) { 43 | case Qt.Key_D: 44 | app.theme = (app.theme === "dark") ? "light" : "dark" 45 | break 46 | case Qt.Key_L: 47 | app.mode = (app.mode === "code") ? "" : "code" 48 | break 49 | } 50 | } 51 | } 52 | } 53 | 54 | Label { 55 | id: hint 56 | anchors { 57 | left: parent.left 58 | bottom: parent.bottom 59 | margins: 8 60 | } 61 | visible: (editor.text.length === 0) 62 | 63 | opacity: 0.35 64 | renderType: Text.NativeRendering 65 | textFormat: TextEdit.PlainText 66 | 67 | text: "Type to project text. Move to next screen with two line breaks.\n" + 68 | "⌘-D: Dark Mode ⌘-L: Plain Text Mode (turn off markdown formatting)" 69 | 70 | onVisibleChanged: { 71 | if (editor.text.length > 0) 72 | hint.visible = false // Disconnecting the property, Make hint display only once. 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /qml/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 6.0 2 | 3 | QtObject { 4 | id: app 5 | 6 | // Properties 7 | property string text 8 | property string theme 9 | property string mode 10 | 11 | // Function 12 | function spawnDisplayWindow(screen) { 13 | let window = windowPrototype.createObject(app) 14 | window.screen = screen 15 | Native.fillScreen(window, screen) 16 | 17 | window.show() 18 | window.raise() 19 | return window 20 | } 21 | 22 | // Events 23 | Component.onCompleted: { 24 | const screens = Native.getScreens() 25 | const primaryScreen = Native.getPrimaryScreen() 26 | 27 | if (screens.length < 2) 28 | spawnDisplayWindow(screens[0]) 29 | else 30 | for (let screen of screens) 31 | if (screen !== primaryScreen) 32 | spawnDisplayWindow(screen) 33 | 34 | let editor = editorPrototype.createObject(app) 35 | editor.screen = primaryScreen 36 | Native.alignAtScreen(editor, primaryScreen) 37 | editor.show() 38 | } 39 | 40 | // Resources 41 | property list resources: [ 42 | Component { 43 | id: windowPrototype 44 | DisplayWindow {} 45 | }, 46 | Component { 47 | id: editorPrototype 48 | EditorWindow {} 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /src/LitNativeHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "LitNativeHandler.h" 2 | 3 | LitNativeHandler::LitNativeHandler(QGuiApplication* parent) 4 | : QObject(parent) 5 | { 6 | this->app = parent; 7 | } 8 | 9 | QVariantList LitNativeHandler::getScreens() { 10 | QVariantList screens; 11 | foreach (QScreen* screen, QGuiApplication::screens()) { 12 | screens << QVariant::fromValue(screen); 13 | QQmlEngine::setObjectOwnership(screen, QQmlEngine::CppOwnership); 14 | } 15 | return screens; 16 | } 17 | 18 | QScreen* LitNativeHandler::getPrimaryScreen() { 19 | return QGuiApplication::primaryScreen(); 20 | } 21 | 22 | void LitNativeHandler::fillScreen(QWindow *window, QScreen *screen) { 23 | window->setGeometry(screen->availableVirtualGeometry()); 24 | } 25 | 26 | void LitNativeHandler::alignAtScreen(QWindow *window, QScreen *screen) { 27 | const QPoint screenCorner = screen->availableVirtualGeometry().bottomRight(); 28 | const QSize windowSize = window->size(); 29 | window->setPosition(screenCorner.x() - windowSize.width(), 30 | screenCorner.y() - windowSize.height()); 31 | } 32 | -------------------------------------------------------------------------------- /src/LitNativeHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef LITNATIVEHANDLER_H 2 | #define LITNATIVEHANDLER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class LitNativeHandler : public QObject 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit LitNativeHandler(QGuiApplication* parent); 15 | Q_INVOKABLE QVariantList getScreens(); 16 | Q_INVOKABLE QScreen* getPrimaryScreen(); 17 | Q_INVOKABLE void fillScreen(QWindow* window, QScreen* screen); 18 | Q_INVOKABLE void alignAtScreen(QWindow* window, QScreen* screen); 19 | Q_INVOKABLE void makeWindowTitleBarTransparent(QWindow* window); 20 | 21 | private: 22 | QGuiApplication* app; 23 | }; 24 | 25 | #endif // LITNATIVEHANDLER_H 26 | -------------------------------------------------------------------------------- /src/LitNativeHandler_mac.mm: -------------------------------------------------------------------------------- 1 | #include 2 | #include "LitNativeHandler.h" 3 | 4 | void LitNativeHandler::makeWindowTitleBarTransparent(QWindow* qWindow) { 5 | NSView *view = (NSView *) reinterpret_cast(qWindow->winId()); 6 | NSWindow *window = view.window; 7 | window.styleMask |= NSWindowStyleMaskFullSizeContentView; 8 | window.titlebarAppearsTransparent = YES; 9 | } 10 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "LitNativeHandler.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QGuiApplication app(argc, argv); 9 | app.setApplicationName("Lit"); 10 | app.setOrganizationName("Poren Chiang"); 11 | app.setOrganizationDomain("lit.poren.tw"); 12 | 13 | LitNativeHandler native(&app); 14 | 15 | QQmlApplicationEngine engine; 16 | engine.rootContext()->setContextProperty("Native", &native); 17 | engine.load(QUrl("qrc:/qml/main.qml")); 18 | 19 | return app.exec(); 20 | } 21 | --------------------------------------------------------------------------------