diff --git a/modules/ags/gtk4/.envrc b/modules/ags/gtk4/.envrc new file mode 100644 index 00000000..674cafba --- /dev/null +++ b/modules/ags/gtk4/.envrc @@ -0,0 +1 @@ +use flake "$FLAKE#node" diff --git a/modules/ags/gtk4/.gitignore b/modules/ags/gtk4/.gitignore new file mode 100644 index 00000000..be93e7dc --- /dev/null +++ b/modules/ags/gtk4/.gitignore @@ -0,0 +1,3 @@ +@girs +node_modules +tsconfig.json diff --git a/modules/ags/gtk4/app.ts b/modules/ags/gtk4/app.ts new file mode 100644 index 00000000..85ac427c --- /dev/null +++ b/modules/ags/gtk4/app.ts @@ -0,0 +1,15 @@ +import { App } from 'astal/gtk4'; + +import style from './style.scss'; + +import Bar from './widget/Bar'; + + +App.start({ + css: style, + instanceName: 'gtk4', + + main() { + Bar(); + }, +}); diff --git a/modules/ags/gtk4/env.d.ts b/modules/ags/gtk4/env.d.ts new file mode 100644 index 00000000..467c0a41 --- /dev/null +++ b/modules/ags/gtk4/env.d.ts @@ -0,0 +1,21 @@ +declare const SRC: string + +declare module "inline:*" { + const content: string + export default content +} + +declare module "*.scss" { + const content: string + export default content +} + +declare module "*.blp" { + const content: string + export default content +} + +declare module "*.css" { + const content: string + export default content +} diff --git a/modules/ags/gtk4/eslint.config.ts b/modules/ags/gtk4/eslint.config.ts new file mode 100644 index 00000000..c8613917 --- /dev/null +++ b/modules/ags/gtk4/eslint.config.ts @@ -0,0 +1,456 @@ +import eslint from '@eslint/js'; +import jsdoc from 'eslint-plugin-jsdoc'; +import stylistic from '@stylistic/eslint-plugin'; +import tseslint from 'typescript-eslint'; + + +export default tseslint.config({ + files: ['**/*.{js,ts,tsx}'], + ignores: ['node_modules/**', 'types/**'], + + extends: [ + eslint.configs.recommended, + jsdoc.configs['flat/recommended-typescript'], + stylistic.configs['recommended-flat'], + ...tseslint.configs.recommended, + ...tseslint.configs.stylistic, + ], + + rules: { + // JSDoc settings + 'jsdoc/tag-lines': ['warn', 'any', { startLines: 1 }], + 'jsdoc/check-line-alignment': ['warn', 'always', { + tags: ['param', 'arg', 'argument', 'property', 'prop'], + }], + 'jsdoc/no-types': 'off', + + // Newer settings + '@typescript-eslint/no-extraneous-class': ['off'], + '@typescript-eslint/no-implied-eval': ['off'], + 'class-methods-use-this': 'off', + '@stylistic/no-multiple-empty-lines': 'off', + '@stylistic/jsx-indent-props': 'off', + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 'error', + '@stylistic/indent-binary-ops': 'off', + '@stylistic/max-statements-per-line': [ + 'error', + { max: 2 }, + ], + + // Pre-flat config + '@typescript-eslint/no-unused-vars': [ + 'error', + { + args: 'all', + argsIgnorePattern: '^_', + caughtErrors: 'all', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + varsIgnorePattern: '^_', + ignoreRestSiblings: true, + }, + ], + + 'array-callback-return': [ + 'error', + { + allowImplicit: true, + checkForEach: true, + }, + ], + 'no-constructor-return': [ + 'error', + ], + 'no-unreachable-loop': [ + 'error', + { + ignore: [ + 'ForInStatement', + 'ForOfStatement', + ], + }, + ], + 'block-scoped-var': [ + 'error', + ], + 'curly': [ + 'warn', + ], + 'default-case-last': [ + 'warn', + ], + 'default-param-last': [ + 'error', + ], + 'eqeqeq': [ + 'error', + 'smart', + ], + 'func-names': [ + 'warn', + 'never', + ], + 'func-style': [ + 'warn', + 'expression', + ], + 'logical-assignment-operators': [ + 'warn', + 'always', + ], + 'no-array-constructor': [ + 'error', + ], + 'no-empty-function': [ + 'warn', + ], + 'no-empty-static-block': [ + 'warn', + ], + 'no-extend-native': [ + 'error', + ], + 'no-extra-bind': [ + 'warn', + ], + 'no-implicit-coercion': [ + 'warn', + ], + 'no-iterator': [ + 'error', + ], + 'no-labels': [ + 'error', + ], + 'no-lone-blocks': [ + 'error', + ], + 'no-lonely-if': [ + 'error', + ], + 'no-loop-func': [ + 'error', + ], + /* + 'no-magic-numbers': [ + 'error', + { + ignore: [ + -1, + 0.1, + 0, + 1, + 2, + 3, + 4, + 5, + 10, + 12, + 33, + 66, + 100, + 255, + 360, + 450, + 500, + 1000, + ], + ignoreDefaultValues: true, + ignoreClassFieldInitialValues: true, + }, + ], + */ + 'no-multi-assign': [ + 'error', + ], + 'no-new-wrappers': [ + 'error', + ], + 'no-object-constructor': [ + 'error', + ], + 'no-proto': [ + 'error', + ], + 'no-return-assign': [ + 'error', + ], + 'no-sequences': [ + 'error', + ], + 'no-shadow': [ + 'error', + { + builtinGlobals: true, + allow: [ + 'Window', + ], + }, + ], + 'no-undef-init': [ + 'warn', + ], + 'no-undefined': [ + 'error', + ], + 'no-useless-constructor': [ + 'warn', + ], + 'no-useless-escape': [ + 'off', + ], + 'no-useless-return': [ + 'error', + ], + 'no-var': [ + 'error', + ], + 'no-void': [ + 'off', + ], + 'no-with': [ + 'error', + ], + 'object-shorthand': [ + 'error', + 'always', + ], + 'one-var': [ + 'error', + 'never', + ], + 'operator-assignment': [ + 'warn', + 'always', + ], + 'prefer-arrow-callback': [ + 'error', + ], + 'prefer-const': [ + 'error', + ], + 'prefer-object-has-own': [ + 'error', + ], + 'prefer-regex-literals': [ + 'error', + ], + 'prefer-template': [ + 'warn', + ], + 'no-prototype-builtins': 'off', + '@typescript-eslint/no-var-requires': [ + 'off', + ], + '@stylistic/array-bracket-newline': [ + 'warn', + 'consistent', + ], + '@stylistic/array-bracket-spacing': [ + 'warn', + 'never', + ], + '@stylistic/arrow-parens': [ + 'warn', + 'always', + ], + '@stylistic/brace-style': [ + 'warn', + 'stroustrup', + { allowSingleLine: true }, + ], + '@stylistic/comma-dangle': [ + 'warn', + 'always-multiline', + ], + '@stylistic/comma-spacing': [ + 'warn', + { + before: false, + after: true, + }, + ], + '@stylistic/comma-style': [ + 'error', + 'last', + ], + '@stylistic/dot-location': [ + 'error', + 'property', + ], + '@stylistic/function-call-argument-newline': [ + 'warn', + 'consistent', + ], + '@stylistic/function-paren-newline': [ + 'warn', + 'consistent', + ], + '@stylistic/indent': [ + 'warn', + 4, + { + SwitchCase: 1, + ignoreComments: true, + ignoredNodes: ['TemplateLiteral > *'], + }, + ], + '@stylistic/key-spacing': [ + 'warn', + { + beforeColon: false, + afterColon: true, + }, + ], + '@stylistic/keyword-spacing': [ + 'warn', + { + before: true, + }, + ], + '@stylistic/linebreak-style': [ + 'error', + 'unix', + ], + '@stylistic/lines-between-class-members': [ + 'warn', + 'always', + { + exceptAfterSingleLine: true, + }, + ], + '@stylistic/max-len': [ + 'warn', + { + code: 105, + ignoreComments: true, + ignoreTrailingComments: true, + ignoreUrls: true, + }, + ], + '@stylistic/multiline-ternary': [ + 'warn', + 'always-multiline', + ], + '@stylistic/new-parens': [ + 'error', + ], + '@stylistic/no-mixed-operators': [ + 'warn', + ], + '@stylistic/no-mixed-spaces-and-tabs': [ + 'error', + ], + '@stylistic/no-multi-spaces': [ + 'error', + ], + '@stylistic/no-tabs': [ + 'error', + ], + '@stylistic/no-trailing-spaces': [ + 'error', + ], + '@stylistic/no-whitespace-before-property': [ + 'warn', + ], + '@stylistic/nonblock-statement-body-position': [ + 'error', + 'below', + ], + '@stylistic/object-curly-newline': [ + 'warn', + { + consistent: true, + }, + ], + '@stylistic/object-curly-spacing': [ + 'warn', + 'always', + ], + '@stylistic/operator-linebreak': [ + 'warn', + 'after', + ], + '@stylistic/padded-blocks': [ + 'error', + 'never', + ], + '@stylistic/padding-line-between-statements': [ + 'warn', + { + blankLine: 'always', + prev: '*', + next: 'return', + }, + { + blankLine: 'always', + prev: [ + 'const', + 'let', + 'var', + ], + next: '*', + }, + { + blankLine: 'any', + prev: [ + 'const', + 'let', + 'var', + ], + next: [ + 'const', + 'let', + 'var', + ], + }, + { + blankLine: 'always', + prev: [ + 'case', + 'default', + ], + next: '*', + }, + ], + '@stylistic/quote-props': [ + 'error', + 'consistent-as-needed', + ], + '@stylistic/quotes': [ + 'error', + 'single', + { + avoidEscape: true, + }, + ], + '@stylistic/semi': [ + 'error', + 'always', + ], + '@stylistic/semi-spacing': [ + 'warn', + ], + '@stylistic/space-before-blocks': [ + 'warn', + ], + '@stylistic/space-before-function-paren': [ + 'warn', + 'never', + ], + '@stylistic/space-infix-ops': [ + 'warn', + ], + '@stylistic/spaced-comment': [ + 'warn', + 'always', + ], + '@stylistic/switch-colon-spacing': [ + 'warn', + ], + '@stylistic/wrap-regex': [ + 'warn', + ], + }, +}); diff --git a/modules/ags/gtk4/package-lock.json b/modules/ags/gtk4/package-lock.json new file mode 100644 index 00000000..2670ba31 Binary files /dev/null and b/modules/ags/gtk4/package-lock.json differ diff --git a/modules/ags/gtk4/package.json b/modules/ags/gtk4/package.json new file mode 100644 index 00000000..3c89ae93 --- /dev/null +++ b/modules/ags/gtk4/package.json @@ -0,0 +1,15 @@ +{ + "name": "ags", + "version": "0.0.0", + "main": "app.ts", + "dependencies": { + "@eslint/js": "9.17.0", + "@stylistic/eslint-plugin": "2.12.1", + "@types/node": "22.10.2", + "eslint": "9.17.0", + "eslint-plugin-jsdoc": "50.6.1", + "fzf": "0.5.2", + "jiti": "2.4.2", + "typescript-eslint": "8.19.0" + } +} diff --git a/modules/ags/gtk4/style.scss b/modules/ags/gtk4/style.scss new file mode 100644 index 00000000..1d0d3a9f --- /dev/null +++ b/modules/ags/gtk4/style.scss @@ -0,0 +1,20 @@ +// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss +$fg-color: #{"@theme_fg_color"}; +$bg-color: #{"@theme_bg_color"}; + +window.Bar { + background: transparent; + color: $fg-color; + font-weight: bold; + + >centerbox { + background: $bg-color; + border-radius: 10px; + margin: 8px; + } + + button { + border-radius: 8px; + margin: 2px; + } +} diff --git a/modules/ags/gtk4/widget/Bar.tsx b/modules/ags/gtk4/widget/Bar.tsx new file mode 100644 index 00000000..dc39b8e2 --- /dev/null +++ b/modules/ags/gtk4/widget/Bar.tsx @@ -0,0 +1,49 @@ +import { App, Astal, Gtk } from 'astal/gtk4'; +import { Variable } from 'astal'; + +import { StyledBox } from './styled-box'; + +const { EXCLUSIVE } = Astal.Exclusivity; +const { TOP, LEFT, RIGHT } = Astal.WindowAnchor; +const { CENTER } = Gtk.Align; + + +const time = Variable(0); + +setInterval(() => { + time.set(time.get() + 1); +}, 1000); + +export default () => { + const styledBox = ( + `* { background: red; min-height: 10px; min-width: ${t}px; }`)} + /> + ) as StyledBox; + + return ( + + + + + {styledBox} + + + + + + ); +}; diff --git a/modules/ags/gtk4/widget/styled-box.ts b/modules/ags/gtk4/widget/styled-box.ts new file mode 100644 index 00000000..20452186 --- /dev/null +++ b/modules/ags/gtk4/widget/styled-box.ts @@ -0,0 +1,34 @@ +import { astalify, Gtk } from 'astal/gtk4'; +import { property, register } from 'astal'; + + +@register({ GTypeName: 'StyledBox' }) +class StyledBoxClass extends Gtk.Box { + declare private _css: string | undefined; + declare private _provider: Gtk.CssProvider | undefined; + + @property(String) + get css(): string | undefined { + return this._css; + } + + set css(value: string) { + if (!this._provider) { + this._provider = new Gtk.CssProvider(); + + this.get_style_context().add_provider( + this._provider, + Gtk.STYLE_PROVIDER_PRIORITY_USER, + ); + } + + this._css = value; + this._provider.load_from_string(value); + } +} + +export type StyledBox = StyledBoxClass; +export const StyledBox = astalify< + StyledBoxClass, + Gtk.Box.ConstructorProps & { css: string } +>(StyledBoxClass); diff --git a/modules/ags/packages.nix b/modules/ags/packages.nix index ff2a5560..7e8fb9a5 100644 --- a/modules/ags/packages.nix +++ b/modules/ags/packages.nix @@ -15,6 +15,7 @@ self: { cfg = config.programs.ags; cfgDesktop = osConfig.roles.desktop; + gtk4ConfigDir = "${cfg.configDir}/../gtk4"; mainPkg = pkgs.writeShellApplication { name = "ags"; @@ -79,6 +80,13 @@ in { packages = [ mainPkg + (pkgs.writeShellApplication { + name = "ags4"; + runtimeInputs = [cfg.package]; + text = '' + exec ags run ~/${gtk4ConfigDir}/app.ts --gtk4 + ''; + }) (pkgs.writeShellApplication { name = "agsConf"; runtimeInputs = [cfg.package]; @@ -108,38 +116,48 @@ in { buildNodeModules buildGirTypes ; + + mkTsConf = gtkVer: let + inherit (ags.packages.${pkgs.system}) gjs; + in + pkgs.writers.writeJSON "tsconfig.json" { + "$schema" = "https://json.schemastore.org/tsconfig"; + "compilerOptions" = { + "experimentalDecorators" = true; + "strict" = true; + "target" = "ES2022"; + "module" = "ES2022"; + "moduleResolution" = "Bundler"; + "noEmit" = true; + "jsx" = "react-jsx"; + "jsxImportSource" = "${gjs}/share/astal/gjs/gtk${toString gtkVer}"; + "paths" = { + "astal" = ["${gjs}/share/astal/gjs"]; + "astal/*" = ["${gjs}/share/astal/gjs/*"]; + }; + }; + }; in ( (buildGirTypes { pname = "ags"; configPath = "${cfg.configDir}/@girs"; packages = cfg.astalLibs; }) + // (buildGirTypes { + pname = "ags"; + configPath = "${gtk4ConfigDir}/@girs"; + packages = cfg.astalLibs; + }) // { "${cfg.configDir}/node_modules".source = buildNodeModules ./config (import ./config).npmDepsHash; - "${cfg.configDir}/tsconfig.json".source = let - inherit (ags.packages.${pkgs.system}) gjs; - in - pkgs.writers.writeJSON "tsconfig.json" { - "$schema" = "https://json.schemastore.org/tsconfig"; - "compilerOptions" = { - "experimentalDecorators" = true; - "strict" = true; - "target" = "ES2023"; - "moduleResolution" = "Bundler"; - "jsx" = "react-jsx"; - "jsxImportSource" = "${gjs}/share/astal/gjs/gtk3"; - "paths" = { - "astal" = ["${gjs}/share/astal/gjs"]; - "astal/*" = ["${gjs}/share/astal/gjs/*"]; - }; - "skipLibCheck" = true; - "module" = "ES2022"; - "lib" = ["ES2023"]; - "noEmit" = true; - }; - }; + "${gtk4ConfigDir}/node_modules".source = + buildNodeModules ./config (import ./config).npmDepsHash; + + "${cfg.configDir}/tsconfig.json".source = mkTsConf 3; + + "${gtk4ConfigDir}/tsconfig.json".source = mkTsConf 4; "${cfg.configDir}/widgets/lockscreen/vars.ts".text = # javascript