diff --git a/demo.ts b/demo.ts index dfb355b52..18833f9d0 100644 --- a/demo.ts +++ b/demo.ts @@ -3,16 +3,20 @@ import { QWidget, QLabel, FlexLayout, - StyleSheet, QPushButton } from "./src/lib/index"; - const win = new QMainWindow(); //------------------------------- const centralWidget = new QWidget(); centralWidget.setObjectName("myroot"); const rootLayout = new FlexLayout(); centralWidget.setLayout(rootLayout); +const view = new QWidget(); +const viewLayout = new FlexLayout(); +view.setInlineStyle( + `margin-top:20px; background: url(${"/Users/atulr/Downloads/photo.jpeg"});` +); +view.setLayout(viewLayout); //-------------------------------------- const label = new QLabel(); label.setObjectName("mylabel"); @@ -22,22 +26,20 @@ const btn = new QPushButton(); btn.setText("Yo button"); btn.setObjectName("btn"); //-------------------------------------- -rootLayout.addWidget(label); -rootLayout.addWidget(btn); +viewLayout.addWidget(label); +viewLayout.addWidget(btn); +rootLayout.addWidget(view); win.setCentralWidget(centralWidget); -const winStyleSheet = StyleSheet.create(` - #myroot { - background-color: #009688; - } +const winStyleSheet = ` #mylabel { font-size: 16px; font-weight: bold; - margin-top: 30px; + margin-top: 40px; } #btn { margin-top: 30px; } -`); +`; win.setStyleSheet(winStyleSheet); win.show(); win.resize(400, 500); diff --git a/docs/api/NodeWidget.md b/docs/api/NodeWidget.md index 76d83364a..a04011d8f 100644 --- a/docs/api/NodeWidget.md +++ b/docs/api/NodeWidget.md @@ -82,6 +82,10 @@ Sets the property that holds the widget's style sheet. It calls the native metho - `styleSheet` string - String which holds the widget's style sheet. Make sure you create this string using `StyleSheet.create()` +#### `widget.styleSheet()` + +Gets the property that holds the widget's style sheet. It calls the native method [QWidget: styleSheet](https://doc.qt.io/qt-5/qwidget.html#styleSheet-prop). + #### `widget.hide()` Hides the widget and its children. It calls the native method [QWidget: hide](https://doc.qt.io/qt-5/qwidget.html#hide). @@ -92,6 +96,10 @@ Sets the object name of the widget in Qt. It calls the native method [QObject: s - `objectName` string - String which holds the widget's object name. +#### `widget.objectName()` + +Gets the property that holds the widget's object name. It calls the native method [QObject: setObjectName](https://doc.qt.io/qt-5/qobject.html#objectName-prop). + #### `widget.setMouseTracking(isMouseTracked)` Sets the property that tells whether mouseTracking is enabled for the widget. It calls the native method [QWidget: mouseTracking](https://doc.qt.io/qt-5/qwidget.html#mouseTracking-prop). @@ -140,3 +148,16 @@ returns the current widget size. It calls the native method [QWidget: size](http #### `widget.updateGeometry()` Notifies the layout system that this widget has changed and may need to change geometry. + +#### `widget.setAttribute(attributeName, switchOn)` + +Sets the attribute attribute on this widget if on is true; otherwise clears the attribute. It calls the native method [QWidget: setAttribute](https://doc.qt.io/qt-5/qwidget.html#setAttribute). + +- `attributeName` WidgetAttribute - Enum from WidgetAttribute. +- `switchOn` - set it to true if you want to enable an attribute. + +#### `widget.testAttribute(attributeName)` + +Returns true if attribute attribute is set on this widget; otherwise returns false. It calls the native method [QWidget: testAttribute](https://doc.qt.io/qt-5/qwidget.html#testAttribute). + +- `attributeName` WidgetAttribute - Enum from WidgetAttribute. diff --git a/examples/calculator/index.ts b/examples/calculator/index.ts index cb5578d22..ca2583466 100644 --- a/examples/calculator/index.ts +++ b/examples/calculator/index.ts @@ -8,7 +8,6 @@ import { import { QLabel } from "../../src/lib/QtWidgets/QLabel"; import { BaseWidgetEvents } from "../../src/lib/core/EventWidget"; import { QKeyEvent } from "../../src/lib/QtGui/QEvent/QKeyEvent"; -import { StyleSheet } from "../../src/lib"; const globals = global as any; @@ -47,8 +46,7 @@ win.addEventListener(BaseWidgetEvents.KeyRelease, nativeEvent => { }); rootView.setObjectName("rootView"); //This is like ids in web world win.setCentralWidget(rootView); -const rootStyleSheet = StyleSheet.create( - ` +const rootStyleSheet = ` * { font-size: 20px; color: white; @@ -111,10 +109,9 @@ QPushButton:pressed { #row2 QPushButton:pressed, #row2 QPushButton:pressed, #row3 QPushButton:pressed, #row4 QPushButton:pressed { background: grey; } -` -); +`; -const operatorStyleSheet = StyleSheet.create(` +const operatorStyleSheet = ` QPushButton { background: #FD8D0E; } @@ -122,7 +119,7 @@ QPushButton { QPushButton:pressed { background: grey; } -`); +`; rootView.setStyleSheet(rootStyleSheet); const rootViewFlexLayout = new FlexLayout(); diff --git a/package-lock.json b/package-lock.json index f99077ca9..00262dd9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -407,6 +407,11 @@ "which": "^1.2.9" } }, + "cuid": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/cuid/-/cuid-2.1.6.tgz", + "integrity": "sha512-ZFp7PS6cSYMJNch9fc3tyHdE4T8TDo3Y5qAxb0KSA9mpiYDo7z9ql1CznFuuzxea9STVIDy0tJWm2lYiX2ZU1Q==" + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", diff --git a/package.json b/package.json index aa6f6bec6..db6145387 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "dependencies": { "@nodegui/test": "^0.0.10", "bindings": "^1.5.0", + "cuid": "^2.1.6", "node-addon-api": "^1.6.3", "node-gyp": "^4.0.0", "postcss-nodegui-autoprefixer": "0.0.7" diff --git a/scripts/automoc.js b/scripts/automoc.js index 6dda49605..11d779482 100644 --- a/scripts/automoc.js +++ b/scripts/automoc.js @@ -1,6 +1,6 @@ const path = require("path"); const fs = require("fs"); -const process = require("child_process"); +const childProcess = require("child_process"); const ROOT_DIR = path.resolve(__dirname, "../"); const MOC_AUTOGEN_DIR = path.resolve(ROOT_DIR, "src/cpp/autogen"); @@ -53,7 +53,7 @@ const main = () => { includeFilePath ); console.log(command); - process.exec(command, error => { + childProcess.exec(command, {}, error => { if (error) { console.error(`exec error: ${error}`); return; diff --git a/src/cpp/QtGui/QWidget/qwidget_macro.h b/src/cpp/QtGui/QWidget/qwidget_macro.h index 17524112e..79fe929aa 100644 --- a/src/cpp/QtGui/QWidget/qwidget_macro.h +++ b/src/cpp/QtGui/QWidget/qwidget_macro.h @@ -57,7 +57,12 @@ Napi::Value setStyleSheet(const Napi::CallbackInfo& info){ \ this->instance->setStyleSheet(style.c_str()); \ return env.Null(); \ } \ -\ +Napi::Value styleSheet(const Napi::CallbackInfo& info){ \ + Napi::Env env = info.Env(); \ + Napi::HandleScope scope(env); \ + QString stylesheet = this->instance->styleSheet(); \ + return Napi::String::New(env, stylesheet.toStdString()); \ +} \ Napi::Value hide(const Napi::CallbackInfo& info) { \ Napi::Env env = info.Env(); \ Napi::HandleScope scope(env); \ @@ -72,6 +77,12 @@ Napi::Value setObjectName(const Napi::CallbackInfo& info){ \ this->instance->setObjectName(QString::fromStdString(objectName.Utf8Value())); \ return env.Null(); \ } \ +Napi::Value objectName(const Napi::CallbackInfo& info){ \ + Napi::Env env = info.Env(); \ + Napi::HandleScope scope(env); \ + QString objectName = this->instance->objectName(); \ + return Napi::String::New(env, objectName.toStdString()); \ +} \ Napi::Value setMouseTracking(const Napi::CallbackInfo& info){ \ Napi::Env env = info.Env(); \ Napi::HandleScope scope(env); \ @@ -137,6 +148,21 @@ Napi::Value size(const Napi::CallbackInfo& info){ \ sizeObj.Set("height", size.height()); \ return sizeObj; \ } \ +Napi::Value setAttribute(const Napi::CallbackInfo& info){ \ + Napi::Env env = info.Env(); \ + Napi::HandleScope scope(env); \ + int attributeId = info[0].As().Int32Value(); \ + bool switchOn = info[1].As().Value(); \ + this->instance->setAttribute(static_cast(attributeId), switchOn); \ + return env.Null(); \ +} \ +Napi::Value testAttribute(const Napi::CallbackInfo& info){ \ + Napi::Env env = info.Env(); \ + Napi::HandleScope scope(env); \ + int attributeId = info[0].As().Int32Value(); \ + bool isOn = this->instance->testAttribute(static_cast(attributeId)); \ + return Napi::Boolean::New(env, isOn); \ +} \ #endif //QWIDGET_WRAPPED_METHODS_DECLARATION @@ -151,8 +177,10 @@ Napi::Value size(const Napi::CallbackInfo& info){ \ InstanceMethod("close",&WidgetWrapName::close), \ InstanceMethod("setLayout",&WidgetWrapName::setLayout), \ InstanceMethod("setStyleSheet",&WidgetWrapName::setStyleSheet), \ + InstanceMethod("styleSheet",&WidgetWrapName::styleSheet), \ InstanceMethod("hide",&WidgetWrapName::hide), \ InstanceMethod("setObjectName",&WidgetWrapName::setObjectName), \ + InstanceMethod("objectName",&WidgetWrapName::objectName), \ InstanceMethod("setMouseTracking",&WidgetWrapName::setMouseTracking), \ InstanceMethod("setEnabled",&WidgetWrapName::setEnabled), \ InstanceMethod("setFixedSize",&WidgetWrapName::setFixedSize), \ @@ -162,5 +190,7 @@ Napi::Value size(const Napi::CallbackInfo& info){ \ InstanceMethod("update",&WidgetWrapName::update), \ InstanceMethod("updateGeometry",&WidgetWrapName::updateGeometry), \ InstanceMethod("size",&WidgetWrapName::size), \ + InstanceMethod("setAttribute",&WidgetWrapName::setAttribute), \ + InstanceMethod("testAttribute",&WidgetWrapName::testAttribute), \ #endif // QWIDGET_WRAPPED_METHODS_EXPORT_DEFINE diff --git a/src/cpp/QtGui/QWidget/qwidget_wrap.cpp b/src/cpp/QtGui/QWidget/qwidget_wrap.cpp index 743fe1ca0..d61996a13 100644 --- a/src/cpp/QtGui/QWidget/qwidget_wrap.cpp +++ b/src/cpp/QtGui/QWidget/qwidget_wrap.cpp @@ -22,7 +22,6 @@ NWidget* QWidgetWrap::getInternalInstance() { QWidgetWrap::QWidgetWrap(const Napi::CallbackInfo& info): Napi::ObjectWrap(info) { Napi::Env env = info.Env(); Napi::HandleScope scope(env); - if(info.Length() == 1) { Napi::Object parentObject = info[0].As(); QWidgetWrap* parentWidgetWrap = Napi::ObjectWrap::Unwrap(parentObject); diff --git a/src/lib/QtEnums/index.ts b/src/lib/QtEnums/index.ts index 7c6193ee6..9f0d1de04 100644 --- a/src/lib/QtEnums/index.ts +++ b/src/lib/QtEnums/index.ts @@ -3,3 +3,89 @@ export enum AspectRatioMode { "KeepAspectRatio", "KeepAspectRatioByExpanding" } + +export enum WidgetAttribute { + WA_AcceptDrops = 78, + WA_AlwaysShowToolTips = 84, + WA_ContentsPropagated = 3, + WA_CustomWhatsThis = 47, + WA_DeleteOnClose = 55, + WA_Disabled = 0, + WA_DontShowOnScreen = 103, + WA_ForceDisabled = 32, + WA_ForceUpdatesDisabled = 59, + WA_GroupLeader = 72, + WA_Hover = 74, + WA_InputMethodEnabled = 14, + WA_KeyboardFocusChange = 77, + WA_KeyCompression = 33, + WA_LayoutOnEntireRect = 48, + WA_LayoutUsesWidgetRect = 92, + WA_MacNoClickThrough = 12, + WA_MacOpaqueSizeGrip = 85, + WA_MacShowFocusRect = 88, + WA_MacNormalSize = 89, + WA_MacSmallSize = 90, + WA_MacMiniSize = 91, + WA_MacVariableSize = 102, + WA_MacBrushedMetal = 46, + WA_Mapped = 11, + WA_MouseNoMask = 71, + WA_MouseTracking = 2, + WA_Moved = 43, + WA_MSWindowsUseDirect3D = 94, + WA_NoChildEventsForParent = 58, + WA_NoChildEventsFromChildren = 39, + WA_NoMouseReplay = 54, + WA_NoMousePropagation = 73, + WA_TransparentForMouseEvents = 51, + WA_NoSystemBackground = 9, + WA_OpaquePaintEvent = 4, + WA_OutsideWSRange = 49, + WA_PaintOnScreen = 8, + WA_PaintUnclipped = 52, + WA_PendingMoveEvent = 34, + WA_PendingResizeEvent = 35, + WA_QuitOnClose = 76, + WA_Resized = 42, + WA_RightToLeft = 56, + WA_SetCursor = 38, + WA_SetFont = 37, + WA_SetPalette = 36, + WA_SetStyle = 86, + WA_ShowModal = 70, + WA_StaticContents = 5, + WA_StyleSheet = 97, + WA_StyleSheetTarget = 131, + WA_TabletTracking = 129, + WA_TranslucentBackground = 120, + WA_UnderMouse = 1, + WA_UpdatesDisabled = 10, + WA_WindowModified = 41, + WA_WindowPropagation = 80, + WA_MacAlwaysShowToolWindow = 96, + WA_SetLocale = 87, + WA_StyledBackground = 93, + WA_ShowWithoutActivating = 98, + WA_NativeWindow = 100, + WA_DontCreateNativeAncestors = 101, + WA_X11NetWmWindowTypeDesktop = 104, + WA_X11NetWmWindowTypeDock = 105, + WA_X11NetWmWindowTypeToolBar = 106, + WA_X11NetWmWindowTypeMenu = 107, + WA_X11NetWmWindowTypeUtility = 108, + WA_X11NetWmWindowTypeSplash = 109, + WA_X11NetWmWindowTypeDialog = 110, + WA_X11NetWmWindowTypeDropDownMenu = 111, + WA_X11NetWmWindowTypePopupMenu = 112, + WA_X11NetWmWindowTypeToolTip = 113, + WA_X11NetWmWindowTypeNotification = 114, + WA_X11NetWmWindowTypeCombo = 115, + WA_X11NetWmWindowTypeDND = 116, + WA_MacFrameworkScaled = 117, + WA_AcceptTouchEvents = 121, + WA_TouchPadAcceptSingleTouchEvents = 123, + WA_X11DoNotAcceptFocus = 126, + WA_AlwaysStackOnTop = 128, + WA_ContentsMarginsRespectsSafeArea = 13 +} diff --git a/src/lib/QtGui/QWidget/index.ts b/src/lib/QtGui/QWidget/index.ts index 28b5c5fdf..bcfce8c53 100644 --- a/src/lib/QtGui/QWidget/index.ts +++ b/src/lib/QtGui/QWidget/index.ts @@ -3,11 +3,17 @@ import { NodeLayout } from "../../QtWidgets/QLayout"; import { EventWidget, BaseWidgetEvents } from "../../core/EventWidget"; import { NativeElement } from "../../core/Component"; import { FlexLayout } from "../../core/FlexLayout"; +import { WidgetAttribute } from "../../QtEnums"; +import { + applyStyleSheet, + StyleSheet, + prepareInlineStyleSheet +} from "../../core/Style/StyleSheet"; // All Widgets should extend from NodeWidget // Implement all native QWidget methods here so that all widgets get access to those aswell export abstract class NodeWidget extends EventWidget { - type = "widget"; layout?: NodeLayout; + type: string = "widget"; show = () => { this.native.show(); }; @@ -26,14 +32,16 @@ export abstract class NodeWidget extends EventWidget { this.native.setLayout(parentLayout.native); this.layout = parentLayout; }; - setStyleSheet = async (style: string | Promise) => { - this.native.setStyleSheet(await style); - setTimeout(() => { - if (this.layout) { - this.layout.invalidate(); // Hackfix: To trigger recalculation - this.layout.update(); - } - }, 20); + setStyleSheet = async (styleSheet: string) => { + const preparedSheet = await StyleSheet.create(styleSheet); + await applyStyleSheet(this, preparedSheet); + }; + setInlineStyle = async (style: string) => { + const preparedSheet = await prepareInlineStyleSheet(this, style); + await applyStyleSheet(this, preparedSheet); + }; + styleSheet = (): string => { + return this.native.styleSheet(); }; hide = () => { this.native.hide(); @@ -41,6 +49,9 @@ export abstract class NodeWidget extends EventWidget { setObjectName = (objectName: string) => { this.native.setObjectName(objectName); }; + objectName = (): string => { + return this.native.objectName(); + }; setMouseTracking = (isMouseTracked: boolean) => { this.native.setMouseTracking(isMouseTracked); }; @@ -68,6 +79,12 @@ export abstract class NodeWidget extends EventWidget { size = (): { width: number; height: number } => { return this.native.size(); }; + setAttribute = (attribute: WidgetAttribute, switchOn: boolean) => { + return this.native.setAttribute(attribute, switchOn); + }; + testAttribute = (attribute: WidgetAttribute): boolean => { + return this.native.testAttribute(attribute); + }; } export class QWidget extends NodeWidget { diff --git a/src/lib/core/Style/StyleSheet.ts b/src/lib/core/Style/StyleSheet.ts index 018c17242..0cf5e2865 100644 --- a/src/lib/core/Style/StyleSheet.ts +++ b/src/lib/core/Style/StyleSheet.ts @@ -1,12 +1,11 @@ import postcss from "postcss"; -import autoprefixer from "postcss-nodegui-autoprefixer"; - +import cuid from "cuid"; +import nodeguiAutoPrefixer from "postcss-nodegui-autoprefixer"; +import { NodeWidget } from "../../QtGui/QWidget"; export class StyleSheet { static create = async (cssString: string): Promise => { - const { css } = await postcss([autoprefixer()]) - .process(cssString, { - from: undefined - }) + const { css } = await postcss([nodeguiAutoPrefixer()]) + .process(cssString, { from: undefined }) .catch(err => { console.warn(`Error autoprefixing`, err); return { css: cssString }; @@ -14,3 +13,32 @@ export class StyleSheet { return css; }; } + +export const prepareInlineStyleSheet = async ( + widget: NodeWidget, + rawStyle: string +) => { + const inlineStyle = await StyleSheet.create(rawStyle); + // Make sure to not calculate ObjectName is the same pass of event loop as other props (incase of react) since the order will matter in that case + // So doing it in multiple passes of event loop allows objectName to be set before using it. The above await solves it. + let cssId = widget.objectName(); + if (!cssId) { + cssId = cuid(); + widget.setObjectName(cssId); + } + return ` + #${cssId} { + ${inlineStyle} + } + `; +}; + +export const applyStyleSheet = async ( + widget: NodeWidget, + styleSheet: string +) => { + widget.native.setStyleSheet(styleSheet); + setTimeout(() => { + widget.layout ? widget.layout.update() : widget.update(); + }, 20); +};