mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
Comprehensively update user manual and contributor guide, add Adam to core team
This commit is contained in:
parent
9eb544df74
commit
93a60daa24
76 changed files with 932 additions and 686 deletions
2
.github/workflows/website.yml
vendored
2
.github/workflows/website.yml
vendored
|
@ -40,7 +40,7 @@ jobs:
|
|||
- name: 🌐 Build Graphite website with Zola
|
||||
run: |
|
||||
cd website
|
||||
zola build
|
||||
zola --config config_prod.toml build
|
||||
|
||||
- name: 🔍 Check if `website/other` directory changed
|
||||
uses: dorny/paths-filter@v3
|
||||
|
|
|
@ -63,7 +63,7 @@ spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu.git" }
|
|||
wgpu-types = "23"
|
||||
wgpu = "23"
|
||||
once_cell = "1.13" # Remove when `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) is stabilized in Rust 1.80 and we bump our MSRV
|
||||
wasm-bindgen = "=0.2.99" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/getting-started/_index.md`. We pin this version because wasm-bindgen upgrades may break various things.
|
||||
wasm-bindgen = "=0.2.99" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/project-setup/_index.md`. We pin this version because wasm-bindgen upgrades may break various things.
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "=0.3.76"
|
||||
web-sys = "=0.3.76"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2021-2023 Graphite Labs, LLC.
|
||||
Copyright (c) 2021-2025 Graphite Labs, LLC.
|
||||
|
||||
The design assets in this directory (including SVG code for icons and logos) are NOT licensed under the Apache 2.0 license terms applied to other Graphite source code files. This directory and its entire contents are excluded from the Apache 2.0 source code license, and full copyright is held by the rightsholder for the creative works contained as files herein.
|
||||
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
# The URL the site will be built for
|
||||
base_url = "https://graphite.rs"
|
||||
|
||||
# Whether to automatically compile all Sass files in the sass directory
|
||||
compile_sass = true
|
||||
|
||||
title = "Graphite"
|
||||
description = "2D raster & vector editor that melds traditional layers & tools with a modern node-based procedural workflow."
|
||||
base_url = "https://graphite.rs"
|
||||
feed_filenames = ["rss.xml"]
|
||||
|
||||
compile_sass = true
|
||||
minify_html = false
|
||||
|
||||
[markdown]
|
||||
# Whether to do syntax highlighting
|
||||
# Theme can be customized by setting the `highlight_theme` variable to a theme supported by Zola
|
||||
highlight_code = true
|
||||
highlight_theme = "css"
|
||||
highlight_themes_css = [{ theme = "kronuz", filename = "syntax-highlighting.css" }]
|
||||
highlight_themes_css = [
|
||||
{ theme = "kronuz", filename = "syntax-highlighting.css" },
|
||||
]
|
||||
|
||||
[extra]
|
||||
# Put all your custom variables here
|
||||
|
|
17
website/config_prod.toml
Normal file
17
website/config_prod.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
title = "Graphite"
|
||||
description = "2D raster & vector editor that melds traditional layers & tools with a modern node-based procedural workflow."
|
||||
base_url = "https://graphite.rs"
|
||||
feed_filenames = ["rss.xml"]
|
||||
|
||||
compile_sass = true
|
||||
minify_html = true
|
||||
|
||||
[markdown]
|
||||
highlight_code = true
|
||||
highlight_theme = "css"
|
||||
highlight_themes_css = [
|
||||
{ theme = "kronuz", filename = "syntax-highlighting.css" },
|
||||
]
|
||||
|
||||
[extra]
|
||||
# Put all your custom variables here
|
|
@ -1,10 +1,11 @@
|
|||
+++
|
||||
title = "Web-based vector graphics editor and design tool"
|
||||
title = "Free online vector editor & procedural design tool"
|
||||
template = "section.html"
|
||||
|
||||
[extra]
|
||||
css = ["index.css"]
|
||||
js = ["image-interaction.js", "video-embed.js"]
|
||||
css_inline = ["index.css"]
|
||||
js = ["carousel.js", "youtube-embed.js", "video-autoplay.js"]
|
||||
meta_description = "Open source free software. A vector graphics creativity suite with a clean, intuitive interface. Opens instantly (no signup) and runs locally in a browser. Exports SVG, PNG, JPG."
|
||||
+++
|
||||
|
||||
<!-- ▛ LOGO ▜ -->
|
||||
|
@ -21,7 +22,7 @@ js = ["image-interaction.js", "video-embed.js"]
|
|||
|
||||
<h1 class="balance-text">Your <span>procedural</span> toolbox for 2D content creation</h1>
|
||||
|
||||
<p class="balance-text">Graphite is a free, open source vector and raster graphics engine, available now in alpha. Get creative with a nondestructive editing workflow that combines layer-based compositing with node-based generative design.</p>
|
||||
<p class="balance-text">Graphite is a free, open source vector and raster graphics editor, available now in alpha. Get creative with a nondestructive editing workflow that combines layer-based compositing with node-based generative design.</p>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
@ -95,7 +96,7 @@ js = ["image-interaction.js", "video-embed.js"]
|
|||
|
||||
<div class="carousel-controls">
|
||||
|
||||
<button class="direction prev" data-carousel-prev>
|
||||
<button class="direction prev" data-carousel-prev aria-label="Move to previous screenshot">
|
||||
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
|
@ -105,12 +106,12 @@ js = ["image-interaction.js", "video-embed.js"]
|
|||
</svg>
|
||||
|
||||
</button>
|
||||
<button class="dot active" data-carousel-dot></button>
|
||||
<button class="dot" data-carousel-dot></button>
|
||||
<button class="dot" data-carousel-dot></button>
|
||||
<button class="dot" data-carousel-dot></button>
|
||||
<button class="dot" data-carousel-dot></button>
|
||||
<button class="direction next" data-carousel-next>
|
||||
<button class="dot active" data-carousel-dot aria-label="Move to screenshot 1"></button>
|
||||
<button class="dot" data-carousel-dot aria-label="Move to screenshot 2"></button>
|
||||
<button class="dot" data-carousel-dot aria-label="Move to screenshot 3"></button>
|
||||
<button class="dot" data-carousel-dot aria-label="Move to screenshot 4"></button>
|
||||
<button class="dot" data-carousel-dot aria-label="Move to screenshot 5"></button>
|
||||
<button class="direction next" data-carousel-next aria-label="Move to next screenshot">
|
||||
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
|
@ -257,7 +258,7 @@ Presently, Graphite is a lightweight offline web app with features primarily ori
|
|||
|
||||
Graphite is designed principally as a professional-grade desktop application that is also accessible in-browser for quick, casual usage.
|
||||
|
||||
Where's the download? Windows, Mac, and Linux apps should be available around the end of 2024. Until then, you can <a href="https://support.google.com/chrome/answer/9658361" target="_blank">install it as a PWA</a>.
|
||||
Where's the download? Windows, Mac, and Linux apps should be available around the start of 2025. Until then, you can <a href="https://support.google.com/chrome/answer/9658361" target="_blank">install it as a PWA</a>.
|
||||
|
||||
Developing and maintaining a native app on so many platforms is a big task. A fast, sloppy approach wouldn't cut it, but engineering the right tech takes time. That's why first supporting just web, the one platform that stays up-to-date and reaches all devices, was the initial priority.
|
||||
|
||||
|
@ -294,7 +295,7 @@ Graphite is the first and only graphic design package built for procedural editi
|
|||
|
||||
<div class="block description">
|
||||
|
||||
<h1 class="feature-box-header balance-text">Explore creative possibilities</h1>
|
||||
<h1 class="feature-box-header balance-text">Explore parametric possibilities</h1>
|
||||
|
||||
Save hours on tedious alterations and make better creative choices. Graphite lets you iterate rapidly by adjusting node parameters instead of individual elements.
|
||||
|
||||
|
@ -311,11 +312,11 @@ Want a different placement area? Just tweak the path.
|
|||
|
||||
<div class="block description">
|
||||
|
||||
<h1 class="feature-box-header balance-text">Mix and morph parameters</h1>
|
||||
<h1 class="feature-box-header balance-text">Mix and morph anything</h1>
|
||||
|
||||
Nondestructive editing means every decision is tied to a parameter you can adjust later on. Use Graphite to interpolate between any states just by dragging sliders.
|
||||
Nondestructive editing means every decision is tied to a parameter you can adjust later on. Use Graphite to interpolate between any states just by dragging value sliders.
|
||||
|
||||
Blend across color schemes. Morph shapes before they're scattered around the canvas. The possibilities are endless.
|
||||
Blend across color schemes. Morph shapes before they're scattered around the canvas. The options are endless.
|
||||
|
||||
<a href="https://editor.graphite.rs/#demo/changing-seasons">Open this artwork</a> and give it a try yourself.
|
||||
|
||||
|
@ -338,15 +339,15 @@ Graphite's representation of artwork as a node graph lets you customize, compose
|
|||
<div class="feature-icons four-wide">
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 9" src="https://static.graphite.rs/icons/icon-atlas-features__2.png" alt="" />
|
||||
<span class="balance-text">Pixelation-free infinite zooming and panning of boundless content</span>
|
||||
<span class="balance-text">Infinitely pan and zoom, export any resolution with no pixelation</span>
|
||||
</div>
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 2" src="https://static.graphite.rs/icons/icon-atlas-features__2.png" alt="" />
|
||||
<span class="balance-text">Modular node-based pipelines for generative AI <em>(soon)</em></span>
|
||||
<span class="balance-text">Modular node-based pipelines for generative AI <em>(future)</em></span>
|
||||
</div>
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 11" src="https://static.graphite.rs/icons/icon-atlas-features__2.png" alt="" />
|
||||
<span class="balance-text">Asset pipelines for studio production environments <em>(soon)</em></span>
|
||||
<span class="balance-text">Asset pipelines for studio production environments <em>(future)</em></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -415,23 +416,23 @@ You'll receive your first newsletter email with the next major Graphite news.
|
|||
<div class="block social-media-links">
|
||||
|
||||
<a href="https://discord.graphite.rs" target="_blank">
|
||||
<img src="https://static.graphite.rs/icons/discord__2.svg" alt="Discord" />
|
||||
<img src="https://static.graphite.rs/icons/discord__2.svg" alt="" />
|
||||
<span class="link not-uppercase arrow">Discord</span>
|
||||
</a>
|
||||
<a href="https://www.reddit.com/r/graphite/" target="_blank">
|
||||
<img src="https://static.graphite.rs/icons/reddit__3.svg" alt="Reddit" />
|
||||
<img src="https://static.graphite.rs/icons/reddit__3.svg" alt="" />
|
||||
<span class="link not-uppercase arrow">Reddit</span>
|
||||
</a>
|
||||
<a href="https://bsky.app/profile/graphiteeditor.bsky.social" target="_blank">
|
||||
<img src="https://static.graphite.rs/icons/bluesky.svg" alt="Bluesky" />
|
||||
<img src="https://static.graphite.rs/icons/bluesky.svg" alt="" />
|
||||
<span class="link not-uppercase arrow">Bluesky</span>
|
||||
</a>
|
||||
<a href="https://twitter.com/graphiteeditor" target="_blank">
|
||||
<img src="https://static.graphite.rs/icons/twitter.svg" alt="Twitter" />
|
||||
<img src="https://static.graphite.rs/icons/twitter.svg" alt="" />
|
||||
<span class="link not-uppercase arrow">Twitter</span>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@GraphiteEditor" target="_blank">
|
||||
<img src="https://static.graphite.rs/icons/youtube.svg" alt="YouTube" />
|
||||
<img src="https://static.graphite.rs/icons/youtube.svg" alt="" />
|
||||
<span class="link not-uppercase arrow">YouTube</span>
|
||||
</a>
|
||||
|
||||
|
@ -511,7 +512,7 @@ Watch this timelapse showing the process of mixing traditional vector art (traci
|
|||
<h1><span class="alternating-text"><span>Co-create</span><span>Ideate</span><span>Illustrate</span><span>Generate</span><span>Iterate</span></span> with Imaginate</h1>
|
||||
|
||||
**Imaginate** is a node powered by <a href="https://en.wikipedia.org/wiki/Stable_Diffusion" target="_blank">Stable Diffusion</a> that makes AI-assisted art creation an easy, nondestructive process.
|
||||
<!-- [Learn how](/learn/node-graph/imaginate) it works. --////////////////////>
|
||||
[Learn how](/learn/node-graph/imaginate) it works.
|
||||
|
||||
</div>
|
||||
<div class="diptych">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
title = "About Graphite"
|
||||
|
||||
[extra]
|
||||
css = ["about.css"]
|
||||
css_inline = ["about.css"]
|
||||
css_external = ["https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap"]
|
||||
+++
|
||||
|
||||
|
@ -111,17 +111,15 @@ It's easy to learn and teach, yet Graphite's accessible design does not sacrific
|
|||
|
||||
---
|
||||
|
||||
<div class="triptych">
|
||||
<div class="diptych">
|
||||
|
||||
<div class="block" id="keavon">
|
||||
|
||||
<img src="https://static.graphite.rs/content/about/core-team-photo-keavon-chambers.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Photo of Keavon Chambers" />
|
||||
|
||||
## Keavon Chambers
|
||||
## Keavon Chambers <span class="handle">(@Keavon)</span> <span class="emoji" title="American">🇺🇸</span>
|
||||
|
||||
*@Keavon* <span class="emoji" title="American">🇺🇸</span>
|
||||
|
||||
*Founder, UI & product design, frontend engineering*
|
||||
***Founder, UI & product design, frontend, editor systems***
|
||||
|
||||
Keavon is a creative generalist with a love for the fusion of arts and technology. UX and graphic designer, photographer, game developer, technical artist, and everything in between— he is equal parts designer and engineer. His multidisciplinary background in the digital arts is aptly suited for concocting the unique vision needed to bring Graphite to fruition.
|
||||
|
||||
|
@ -130,35 +128,46 @@ Keavon is a creative generalist with a love for the fusion of arts and technolog
|
|||
|
||||
<img src="https://static.graphite.rs/content/about/core-team-photo-dennis-kobert.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Photo of Dennis Kobert" />
|
||||
|
||||
## Dennis Kobert
|
||||
## Dennis Kobert <span class="handle">(@TrueDoctor)</span> <span class="emoji" title="German">🇩🇪</span>
|
||||
|
||||
*@TrueDoctor* <span class="emoji" title="German">🇩🇪</span>
|
||||
|
||||
*Graphene node engine, research, architecture*
|
||||
***Graphene node engine, research, architecture***
|
||||
|
||||
Dennis is a mix between a mathematician and a mad scientist. While still enjoying the art of photography and image editing (which drew him to the project early on), he thrives when challenged with designing complex systems and pushing boundaries. His method of building generalized solutions wrapped in elegant layers of abstraction led to his creation of the Graphene engine.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="diptych">
|
||||
|
||||
<div class="block" id="hypercube">
|
||||
|
||||
<img src="https://static.graphite.rs/content/about/core-team-photo-hypercube.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Photo of Hypercube" />
|
||||
<img src="https://static.graphite.rs/content/about/core-team-photo-hypercube__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Photo of Hypercube" />
|
||||
|
||||
## "Hypercube"
|
||||
## "Hypercube" <span class="handle">(@0Hypercube)</span> <span class="emoji" title="British">🇬🇧</span>
|
||||
|
||||
*@0Hypercube* <span class="emoji" title="British">🇬🇧</span>
|
||||
|
||||
*Editor systems, nodes, tools, architecture*
|
||||
***Editor systems, nodes, tools, architecture***
|
||||
|
||||
"Hypercube" is a light speed code monkey who excels at developing, refactoring, and maintaining the editor codebase. With an unmatched ability to comprehend intricate code, he delivers lasting and efficient solutions at an impressive pace. He takes ownership of many central editor systems including tools, typography, transforms, layers, and node graph integration.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="block" id="adam">
|
||||
|
||||
<img src="https://static.graphite.rs/content/about/core-team-photo-adam-gerhant.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Photo of Adam Gerhant" />
|
||||
|
||||
## Adam Gerhant <span class="handle">(@pendapia)</span> <span class="emoji" title="American">🇺🇸</span>
|
||||
|
||||
***Editor graph tooling, node data formats***
|
||||
|
||||
Adam is a pragmatic problem solver with a talent for simplifying complexity. He is responsible for various architectural decisions which provide future proof integrations between the Graphene engine and editor. His work has greatly improved the performance, stability and code quality of the project, while also setting the stage for additional features.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<div class="triptych">
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ summary = "Looking back on 2023, we reflect on our significant achievements and
|
|||
reddit = "https://www.reddit.com/r/graphite/comments/18xmoti/blog_post_looking_back_on_2023_and_whats_next/"
|
||||
twitter = "https://twitter.com/GraphiteEditor/status/1742576805532577937"
|
||||
|
||||
js = ["video-embed.js"]
|
||||
js = ["youtube-embed.js"]
|
||||
+++
|
||||
|
||||
The new year is here, and with so many accomplishments to share from the past twelve months, let's revisit the highlights of 2023 for the Graphite project. Now that winter has entered, let's swing back to the spring, summarize the summer, and follow this fall's noteworthy developments that brought another year of fruitful progress to Graphite's mission of re-envisioning artists' 2D creative workflows with the best free software we can build for the open source community. This past year as a team, we all got closer— to one another from continents apart; to visiting and connecting with our industry peers; and to reaching exciting new development milestones.
|
||||
|
|
|
@ -18,8 +18,8 @@ Calling Rust and computer graphics developers: spend your summer contributing to
|
|||
|
||||
[Google Summer of Code](https://summerofcode.withgoogle.com/), now entering its 20th year, is a worldwide program that supports students getting involved in open source software development. Like other forms of summer internships, GSoC pays a [stipend](https://developers.google.com/open-source/gsoc/help/student-stipends) for participants to work on self-contained projects with the direction and guidance of experienced mentors. The program is directed towards university students but is open to anyone 18 and older. The [timeline's](https://developers.google.com/open-source/gsoc/timeline) start date is May 27 and most projects run for 12 weeks, but there is some flexibility towards pacing and end dates.
|
||||
|
||||
We are looking for reasonably experienced, self-motivated, fast-learning students who are interested in Rust and/or computer graphics (and for one project, machine learning). Full [project descriptions](/volunteer/guide/projects/student-projects/#project-idea-list) are listed and we encourage prospective applicants to reach out right away to maximize the chance of acceptance.
|
||||
We are looking for reasonably experienced, self-motivated, fast-learning students who are interested in Rust and/or computer graphics (and for one project, machine learning). Full [project descriptions](/volunteer/guide/student-projects/#project-idea-list) are listed and we encourage prospective applicants to reach out right away to maximize the chance of acceptance.
|
||||
|
||||
Additional year-round opportunities are available for student capstone or independent research projects. These are run similarly, but are also available to multi-person groups and provide academic credit instead of a stipend. To date, three were [completed](/volunteer/guide/projects/student-projects/#successful-past-projects) successfully.
|
||||
Additional year-round opportunities are available for student capstone or independent research projects. These are run similarly, but are also available to multi-person groups and provide academic credit instead of a stipend. To date, three were [completed](/volunteer/guide/student-projects/#successful-past-projects) successfully.
|
||||
|
||||
It is our hope that GSoC will help Graphite grow and provide valuable learning outcomes for participants. A win-win! If this is an opportunity that's right for you, begin [getting involved](/volunteer/guide/projects/student-projects/).
|
||||
It is our hope that GSoC will help Graphite grow and provide valuable learning outcomes for participants. A win-win! If this is an opportunity that's right for you, begin [getting involved](/volunteer/guide/student-projects/).
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
title = "Donate"
|
||||
|
||||
[extra]
|
||||
# css = ["donate.css"]
|
||||
# css_inline = ["donate.css"]
|
||||
# js = ["fundraising.js"]
|
||||
+++
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
title = "Graphite features"
|
||||
|
||||
[extra]
|
||||
css = ["features.css"]
|
||||
css_inline = ["features.css"]
|
||||
+++
|
||||
|
||||
<section>
|
||||
|
@ -12,7 +12,7 @@ css = ["features.css"]
|
|||
|
||||
The current alpha version of Graphite is a tool for vector art and graphic design. It also supports a limited, experimental raster editing toolset. All this is built around a central node graph that stores layer data and provides a basic—but continually improving—procedural design and nondestructive editing workflow which is a unique feature among vector editing software.
|
||||
|
||||
Throughout 2024, stay tuned for major performance improvements, a multiplatform desktop app with native rendering speed, and the beginnings of a full suite of raster editing tools.
|
||||
Throughout 2025, stay tuned for major performance improvements, a multiplatform desktop app with native rendering speed, and the beginnings of a full suite of raster editing tools.
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
@ -137,10 +137,10 @@ Always on the bleeding edge and built to last— Graphite is written on a robust
|
|||
<img class="atlas" style="--atlas-index: 26" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Interactive graph auto-layout</span>
|
||||
</div>
|
||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
||||
<!-- <div class="feature-icon ongoing" title="Development Ongoing">
|
||||
<img class="atlas" style="--atlas-index: 0" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Imaginate tool</span>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
||||
<img class="atlas" style="--atlas-index: 14" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Infinitely zoomable/panable content</span>
|
||||
|
@ -149,14 +149,14 @@ Always on the bleeding edge and built to last— Graphite is written on a robust
|
|||
<img class="atlas" style="--atlas-index: 12" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>GPU-accelerated raster rendering</span>
|
||||
</div>
|
||||
<!-- Alpha 4 -->
|
||||
<div class="feature-icon heading" title="Expected to begin February 2025" data-year="Feb. 2025">
|
||||
<h3>— Alpha 4 —</h3>
|
||||
</div>
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 7" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Lightweight desktop app (with <a target="_blank" href="https://tauri.app/">Tauri</a>)</span>
|
||||
</div>
|
||||
<!-- Alpha 4 -->
|
||||
<div class="feature-icon heading" title="Expected to begin February 2025" data-year="2025">
|
||||
<h3>— Alpha 4 —</h3>
|
||||
</div>
|
||||
<div class="feature-icon">
|
||||
<img class="atlas" style="--atlas-index: 9" src="https://static.graphite.rs/icons/icon-atlas-roadmap__3.png" alt="" />
|
||||
<span>Graph data attribute spreadsheets</span>
|
||||
|
|
|
@ -5,27 +5,27 @@ page_template = "book.html"
|
|||
|
||||
[extra]
|
||||
book = true
|
||||
js = ["video-embed.js"]
|
||||
js = ["youtube-embed.js"]
|
||||
+++
|
||||
|
||||
**Welcome to the Graphite user manual.** Get ready to learn how the software can help bring your 2D creative ideas to life.
|
||||
Welcome to the Graphite user manual. Keep reading to to learn how the software can help bring your 2D creative ideas to life.
|
||||
|
||||
You may choose to read this sequentially and learn from the structured introduction and sample projects. Or you may jump to chapters of interest that also serve as quick reference.
|
||||
|
||||
## More chapters coming soon
|
||||
## More chapters on the way
|
||||
|
||||
Additional manual chapters are being added over time. Check back often!
|
||||
Additional manual chapters are being added over time. Check back every so often.
|
||||
|
||||
## Need help?
|
||||
|
||||
If you're ever stuck or confused, ask your questions in the `#🧭user-help` channel of the [Graphite Discord server](https://discord.graphite.rs) or post a thread in the [discussion board](https://github.com/GraphiteEditor/Graphite/discussions) on GitHub.
|
||||
|
||||
## Jump right in
|
||||
|
||||
<!-- If you're eager to skip the reading, head straight to the [hands-on quickstart video](./introduction) in the next chapter for a beginner project walkthrough you can follow along with. -->
|
||||
|
||||
The fastest way to get started is to watch and follow along with the steps in the hands-on quickstart video:
|
||||
The fastest way to get started is to watch and follow along steps-by-step in the hands-on quickstart video:
|
||||
|
||||
<div class="video-embed aspect-16x9">
|
||||
<img data-video-embed="7gjUhl_3X10" src="https://static.graphite.rs/content/learn/introduction/tutorial-1-vector-art-quickstart-youtube.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Graphite Tutorial 1 - Hands-On Quickstart" />
|
||||
</div>
|
||||
|
||||
## Need help?
|
||||
|
||||
If you're ever stuck or confused, ask your questions in the `#🧭user-help` channel of the [Graphite Discord server](https://discord.graphite.rs) or post a thread in the [discussion board](https://github.com/GraphiteEditor/Graphite/discussions) on GitHub.
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
+++
|
||||
title = "Graph"
|
||||
template = "book.html"
|
||||
page_template = "book.html"
|
||||
|
||||
[extra]
|
||||
order = 4
|
||||
+++
|
||||
|
||||
- Opening the graph
|
||||
- Document graph vs. layer graph, limitations
|
||||
- Adding nodes
|
||||
- Connecting nodes
|
34
website/content/learn/_unpublished/graph/_index.md
Normal file
34
website/content/learn/_unpublished/graph/_index.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
+++
|
||||
title = "Graph"
|
||||
template = "book.html"
|
||||
page_template = "book.html"
|
||||
|
||||
[extra]
|
||||
order = 4
|
||||
+++
|
||||
|
||||
- Opening the graph
|
||||
- Document graph vs. layer graph, limitations
|
||||
- Adding nodes
|
||||
- Connecting nodes
|
||||
|
||||
## Overlaid node graph editing
|
||||
|
||||
Opening the overlaid node graph shows the structure of nodes and layers that compose the document artwork. It's a more detailed view of what the [Layers](../layers-panel) and [Properties](../properties-panel) panels show.
|
||||
|
||||
**Nodes** are the entities with left-to-right input **connectors**.
|
||||
|
||||
**Layers** are the larger entities shown with thumbnails and a bottom-to-top direction of data stacking. Their purpose is to composite sources of graphical data on top of one another in a **layer stack**. Layers take input from other nodes or layers via a connector on their left side. When that connector is fed by another layer stack, the Layers panel considers it a **group** because it combines one stack into another parent stack.
|
||||
|
||||
Layers and nodes are wired together using **links** which send data between the outputs of nodes to the inputs of others. You can wire up a node by dragging from the output connector of one node to the input connector of its destination node. But note that forming cyclic graphs, where a loop can be traced along the links of a set of nodes, is not permitted. Graphical data flows into the **Output** node which then becomes rendered to the document viewport.
|
||||
|
||||
### Node/layer controls
|
||||
|
||||
When a layer or node is selected, these buttons will show up on the left side of the control bar:
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/document-panel/node-controls-buttons.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="The node/layer controls" /></p>
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| Make<span> </span>Hidden/<br />Make<span> </span>Visible | <p>Toggles the visibility state of the layer or node. This is equivalent to the eye icon button displayed beside each layer. If a node or layer is hidden, it gets bypassed in the data flow. <kbd>Ctrl</kbd><kbd>H</kbd> (macOS: <kbd>⌘</kbd><kbd>H</kbd>) is a shortcut for this toggle that can be used from the graph or viewport.</p> |
|
||||
| Preview/<br />End<span> </span>Preview | <p>Temporarily moves the graph output away from the Output node and the graph output is instead provided by the previewed node. While previewing, the node is styled with a dashed, brighter border. Ending the preview returns responsibility back to the Output node. This is a handy feature for viewing part of a graph without needing to disconnect the actual Output node and manually restore it later. Clicking a node or layer in the graph while holding <kbd>Alt</kbd> is a shortcut for toggling its preview.</p> |
|
|
@ -8,7 +8,7 @@ order = 3
|
|||
This manual page hasn't been written yet! But here is an outline for what's coming soon:
|
||||
|
||||
- About layers and folders
|
||||
- Top bar
|
||||
- Control bar
|
||||
- Blend modes and opacity
|
||||
- Adding folders
|
||||
- Deleting the selected layers
|
|
@ -3,7 +3,7 @@ title = "Imaginate"
|
|||
|
||||
[extra]
|
||||
order = 2
|
||||
js = ["image-interaction.js"]
|
||||
js = ["carousel.js"]
|
||||
+++
|
||||
|
||||
Imaginate is a useful tool at every stage in the artistic process. Early on it provides inspiration for styles, color palettes, subjects, and composition. It lets you quickly test ideas and explore artistic directions. It's also a useful way to generate placeholder images and content for kit bashing.
|
|
@ -21,37 +21,41 @@ On the left, the [**menu bar**](./menu-bar) provides quick access to many editor
|
|||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The menu bar" /></p>
|
||||
|
||||
In the (forthcoming) macOS desktop release, the menu bar is absent from the editor window; its functions are instead located in macOS menu bar.
|
||||
<!-- In the (forthcoming) macOS desktop release, the menu bar is absent from the editor window; its functions are instead located in macOS menu bar. -->
|
||||
|
||||
### Document title
|
||||
|
||||
In the center, the **document title** displays the name of the active document. That name is given a `*` suffix if the file has unsaved changes. For example, *Painting.graphite** would be unsaved but *Painting.graphite* would have no changes since it was last saved.
|
||||
In the center, the **document title** displays the name of the active document. That name is given a `*` suffix if the file has unsaved changes. For example, *Painting.graphite** would be unsaved but *Painting.graphite* would have no changes following its last save.
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/document-title.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The document title" /></p>
|
||||
|
||||
### Window buttons
|
||||
|
||||
On the right, the **window buttons** provide platform-specific controls for the application window. In the (forthcoming) macOS desktop release, this appears on the left side instead.
|
||||
On the right, the **window buttons** provide platform-specific controls for the application.
|
||||
|
||||
<!-- In the (forthcoming) macOS desktop release, this appears on the left side instead. -->
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| **Web** | A button to enter fullscreen mode is displayed.<br /><br />The label "*Go fullscreen to access all hotkeys*" indicates that some shortcut keys like <kbd>Ctrl</kbd><kbd>N</kbd> (macOS: <kbd>⌘</kbd><kbd>N</kbd>) are reserved by the web browser and can only be used in fullscreen mode. (An alternative to fullscreen mode: include <kbd>Alt</kbd> in the shortcut combinations for browser-reserved hotkeys.)<br /><br /><img src="https://static.graphite.rs/content/learn/interface/window-buttons-web.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Fullscreen button" /> |
|
||||
| **Windows<br />& Linux** | The standard window controls are displayed: minimize, maximize/restore down, and close.<br /><br /><img src="https://static.graphite.rs/content/learn/interface/window-buttons-windows-linux.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Minimize/maximize/close window buttons" /> |
|
||||
| **macOS** | The standard window controls are displayed: close, minimize, and fullscreen. These are located on the left of the title bar.<br /><br /><img src="https://static.graphite.rs/content/learn/interface/window-buttons-macos.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Close/minimize/fullscreen window buttons" /> |
|
||||
| **Web** | <p>A button to enter fullscreen mode is displayed.</p><p>The label "*Go fullscreen to access all hotkeys*" indicates that some shortcut keys like <kbd>Ctrl</kbd><kbd>N</kbd> (macOS: <kbd>⌘</kbd><kbd>N</kbd>) are reserved by the web browser and can only be used in fullscreen mode. (An alternative to going fullscreen: include <kbd>Alt</kbd> in the shortcut combinations for browser-reserved hotkeys.)</p><p><img src="https://static.graphite.rs/content/learn/interface/window-buttons-web.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Fullscreen button" /></p> |
|
||||
<!-- | **Windows<br />& Linux** | The standard window controls are displayed: minimize, maximize/restore down, and close.<br /><br /><img src="https://static.graphite.rs/content/learn/interface/window-buttons-windows-linux.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Minimize/maximize/close window buttons" /> | -->
|
||||
<!-- | **macOS** | The standard window controls are displayed: close, minimize, and fullscreen. These are located on the left of the title bar.<br /><br /><img src="https://static.graphite.rs/content/learn/interface/window-buttons-macos.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Close/minimize/fullscreen window buttons" /> | -->
|
||||
|
||||
## Workspace
|
||||
|
||||
The **workspace** is the editor's main content area. It houses the **panels** packed next to one another. The **gutter** lines between neighboring panels may be dragged to resize them.
|
||||
The **workspace** is the editor's main content area, filled with **panels** arranged next to one another. The **gutter** lines, located between neighboring panels, may be dragged to resize them.
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/workspace__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The workspace" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/workspace__3.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The workspace" /></p>
|
||||
|
||||
### Panels
|
||||
|
||||
Panels are regions of the UI dedicated to a specific purpose. [**Document**](./document-panel), [**Properties**](./properties-panel), and [**Layers**](./layers-panel) are presently the three panel types. Each will be covered later in the chapter.
|
||||
Panels are regions of the UI dedicated to a specific purpose. [**Document**](./document-panel), Properties, and Layers are presently the three panel types.
|
||||
|
||||
Each panel name is shown in its **panel tab bar**. Panel tabs provide a quick way to swap between multiple panels occupying the same area (currently only documents support this). Down the road, these tabs will be dockable so the default layout may be customized.
|
||||
Each panel name is shown in its **panel header**. Panel tabs offer a quick way to swap between multiple panels occupying the same area (currently only documents support this).
|
||||
|
||||
Beneath the panel tab bar, the **panel body** displays the content for its panel type. Each will be described in the following pages.
|
||||
Down the road, these tabs will be dockable so the default layout may be customized.
|
||||
|
||||
Beneath the panel header, the **panel content** displays the content for its panel type. Each will be described in the following pages.
|
||||
|
||||
## Status bar
|
||||
|
||||
|
@ -75,6 +79,7 @@ The following chart describes each icon representing the mouse inputs you can pe
|
|||
|
||||
| | Clicks | Drags | Others |
|
||||
|-|:-:|:-:|:-:|
|
||||
| **Left<br />mouse<br />button** | Left click<br /><br /><img src="https://static.graphite.rs/content/learn/interface/mouse-input-left-click.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Left click icon" /> | Left click drag<br /><br /><img src="https://static.graphite.rs/content/learn/interface/mouse-input-left-click-drag.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Left click drag icon" /> | Left double-click<br /><br /><img src="https://static.graphite.rs/content/learn/interface/mouse-input-left-double-click.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Left double-click icon" /> |
|
||||
| **Right<br />mouse<br />button** | Right click<br /><br /><img src="https://static.graphite.rs/content/learn/interface/mouse-input-right-click.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Right click icon" /> | Right click drag<br /><br /><img src="https://static.graphite.rs/content/learn/interface/mouse-input-right-click-drag.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Right click drag icon" /> | Right double-click<br /><br /><img src="https://static.graphite.rs/content/learn/interface/mouse-input-right-double-click.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Right double-click icon" /> |
|
||||
| **Middle<br />mouse<br />button** | Middle click<br /><br /><img src="https://static.graphite.rs/content/learn/interface/mouse-input-middle-click.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Middle click icon" /> | Middle click drag<br /><br /><img src="https://static.graphite.rs/content/learn/interface/mouse-input-middle-click-drag.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Middle click drag icon" /> | Scroll up/down<br /><br /><img src="https://static.graphite.rs/content/learn/interface/mouse-input-scroll-up.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Scroll up icon" /> <img src="https://static.graphite.rs/content/learn/interface/mouse-input-scroll-down.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Scroll down icon" /> |
|
||||
| | | <img src="https://static.graphite.rs/content/learn/interface/mouse-icon-drag.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Drag icon" /><br style="line-height: 4" />Drag | <img src="https://static.graphite.rs/content/learn/interface/mouse-icon-stationary.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Mouse kept stationary icon" /><br style="line-height: 4" />Stationary |
|
||||
| **Left<br />mouse<br />button** | <img src="https://static.graphite.rs/content/learn/interface/mouse-icon-left-click.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Left click icon" /><br style="line-height: 4" />Left click | <img src="https://static.graphite.rs/content/learn/interface/mouse-icon-left-click-drag.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Left click drag icon" /><br style="line-height: 4" />Left click drag | <img src="https://static.graphite.rs/content/learn/interface/mouse-icon-left-double-click.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Left double-click icon" /><br style="line-height: 4" />Left double-click |
|
||||
| **Right<br />mouse<br />button** | <img src="https://static.graphite.rs/content/learn/interface/mouse-icon-right-click.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Right click icon" /><br style="line-height: 4" />Right click | <img src="https://static.graphite.rs/content/learn/interface/mouse-icon-right-click-drag.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Right click drag icon" /><br style="line-height: 4" />Right click drag | <img src="https://static.graphite.rs/content/learn/interface/mouse-icon-right-double-click.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Right double-click icon" /><br style="line-height: 4" />Right double-click |
|
||||
| **Middle<br />mouse<br />button** | <img src="https://static.graphite.rs/content/learn/interface/mouse-icon-middle-click.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Middle click icon" /><br style="line-height: 4" />Middle click | <img src="https://static.graphite.rs/content/learn/interface/mouse-icon-middle-click-drag.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Middle click drag icon" /><br style="line-height: 4" />Middle click drag | <img src="https://static.graphite.rs/content/learn/interface/mouse-icon-scroll.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Scroll up/down icons" /><br style="line-height: 4" />Scroll up/down |
|
||||
|
|
|
@ -5,131 +5,94 @@ title = "Document panel"
|
|||
order = 2
|
||||
+++
|
||||
|
||||
The **Document panel** is the main content area where the artwork is displayed and edited using **tools** within the **viewport**. It's also where the **node graph** can be overlaid by pressing <kbd>Ctrl</kbd><kbd>Space</kbd>. The viewport is for interactive, visual editing of the **canvas**. The node graph is where you can inspect the underlying structure of the document and edit it in a more technical way if the need arises.
|
||||
The **Document panel** is the main content area where the artwork is displayed and edited using **tools** within the **viewport**. It's also where the **node graph** can be overlaid by pressing <kbd>Ctrl</kbd><kbd>Space</kbd>.
|
||||
|
||||
The viewport is for interactive, visual editing of the **canvas**. The node graph is where you can inspect the underlying structure of the document and edit it in a more technical manner when the need arises.
|
||||
|
||||
There is one instance of the Document panel per open document file. Each has its own tab labeled with its file name. When a document has unsaved changes, an `*` is included at the end of the name.
|
||||
|
||||
The Document panel is composed of three main areas:
|
||||
|
||||
- The **top bar** runs across the top of the panel and provides controls and view options.
|
||||
- The **shelf** is the narrow vertical bar that runs down the left of the panel and lists a selection of tools or nodes.
|
||||
- The **table** fills the rest of the panel and contains the viewport and overlaid node graph.
|
||||
- The **control bar** runs across the top of the panel and shows tool and viewport controls.
|
||||
- The **tool shelf** is the vertical bar that runs down the left of the panel showing the editing tools and working colors.
|
||||
- The **viewport** fills most of the panel and contains a view of the canvas which can be interactively edited using the tools.
|
||||
|
||||
The content of each depends if the viewport or node graph is visible, as described in the two sections below.
|
||||
## Control bar
|
||||
|
||||
## Interactive viewport editing
|
||||
Here is where you control your interaction with the document via active tool and view options.
|
||||
|
||||
### Top bar
|
||||
|
||||
While the viewport is visible, the left of the bar provides controls for the active tool and the right provides view options.
|
||||
|
||||
#### Editing modes
|
||||
<!--
|
||||
### Editing modes
|
||||
|
||||
Only the default mode is currently implemented. Others will be added in the future and this dropdown is a placeholder for that.
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| <img src="https://static.graphite.rs/content/learn/interface/document-panel/editing-modes__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="The editing modes dropdown menu" /> | The default, **Design Mode**, is for directly editing the artwork.<br /><br />Once implemented, **Select Mode** will be where marquee selections are made to constrain the active tool's edits to a masked area of choice.<br /><br />Once implemented, **Guide Mode** will be for creating guides and constraint systems used for alignment and constraint-based layout. |
|
||||
| <img src="https://static.graphite.rs/content/learn/interface/document-panel/editing-modes__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="The editing modes dropdown menu" /> | The default, **Design Mode**, is for directly editing the artwork.<br /><br />Once implemented, **Select Mode** will be where marquee selections are made to constrain the active tool's edits to a masked area of choice.<br /><br />Once implemented, **Guide Mode** will be for creating guides and constraint systems used for alignment and constraint-based layout. |
|
||||
-->
|
||||
|
||||
#### Tool options
|
||||
### Tool controls
|
||||
|
||||
Provides controls for the active tool. These change with each tool, and are blank for some.
|
||||
The left side of the control bar has controls pertaining to the active tool. It's empty for some tools.
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/document-panel/tool-options__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Example of the tool options for the Select tool" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/document-panel/tool-options__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Example of the tool controls for the Select tool" /></p>
|
||||
|
||||
Pictured above is the tool options for the Select tool. It provides options related to its selection behavior and offers useful action buttons for modifying the selected layers with alignment, flipping, and (not-yet-implemented) boolean operations.
|
||||
The example above shows the Select tool controls. Because the Select tool deals with selecting and transforming layers, its controls include:
|
||||
- The selection mode to determine if the deepest or shallowest layer in a nested hierarchy is selected when clicked.
|
||||
- The point about which the **transform cage** is centered.
|
||||
- Actions to align and distribute the selected layers amongst themselves.
|
||||
- Actions to flip the selected layers horizontally or vertically.
|
||||
- Actions to cut or combine selected layers with themselves using boolean operations.
|
||||
|
||||
<!-- Each tool's options are described in the [Tools](../../tools) chapter. -->
|
||||
Each tool's options will be described in the upcoming tools chapter.
|
||||
Depending on which tool is active, these will change to reflect the pertinent options.
|
||||
|
||||
#### Viewport options
|
||||
### Viewport controls
|
||||
|
||||
Shows options for how the viewport is displayed and interacted with.
|
||||
The right side of the control bar has controls related to the active document and viewport.
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/document-panel/viewport-options__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The viewport options" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/document-panel/viewport-options__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The viewport controls" /></p>
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| Overlays | When checked (default), overlays are shown. When unchecked, they are hidden. Overlays are the contextual visualizations (like bounding boxes and vector points) that appear atop the viewport when using tools. |
|
||||
| Snapping | When checked (default), drawing and dragging shapes and vector points means they will snap to other areas of geometric interest from other layers, like corners or anchor points. When unchecked, the selection moves freely.<br /><br />Fine-grained options are available by clicking the overflow button to access its options popover menu:<br /><br /><img src="https://static.graphite.rs/content/learn/interface/document-panel/snapping-popover__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="Snapping options popover menu" /><ul><li>**Bounding Boxes** sets whether the *edges* and *centers* of the rectangle that encloses the bounds of each other layer is used for snapping.</li><li>**Geometry** sets whether the anchors and handles of vector paths are used for snapping.</li></ul> |
|
||||
| Grid | When checked (off by default), grid lines are shown to which drawn and edited shapes are snapped to. The initial grid scale is 1 document unit, helping you draw pixel-perfect artwork.<ul><li>**Origin** is the position where the repeating grid pattern begins from.</li><li>**Type** sets whether the grid pattern is made of squares or triangles.<br /><br />**Rectangular** is a square pattern:<br /><br /><img src="https://static.graphite.rs/content/learn/interface/document-panel/grid-rectangular-popover.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="Snapping options popover menu" /><br /><ul><li>**Spacing** is the width and height of the square grid cells.</li></ul><br /><br />**Isometric** is a triangle pattern:<br /><br /><img src="https://static.graphite.rs/content/learn/interface/document-panel/grid-isometric-popover.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="Snapping options popover menu" /><br /><ul><li>**Y Spacing** is the height between vertical repetitions of the grid.</li><li>**Angles** is the slant of the upward and downward sloped grid lines.</li></ul></li></ul> |
|
||||
| View Mode | **Normal** (default): The artwork is rendered normally.<br /><br />**Outline**: The artwork is rendered as a wireframe.<br /><br />**Pixels**: **Not implemented yet.** The artwork is rendered as it would appear when exported as a bitmap image at 100% scale regardless of the viewport zoom level. |
|
||||
| Zoom In | Zooms the viewport in to the next whole increment. |
|
||||
| Zoom Out | Zooms the viewport out to the next whole increment. |
|
||||
| Reset Tilt and Zoom to 100% | Resets the viewport tilt to 0°. Resets the viewport zoom to 100% which matches the canvas and viewport pixel scale 1:1. |
|
||||
| Viewport Zoom | Indicates the current zoom level of the viewport and allows precise values to be chosen. |
|
||||
| Viewport Tilt | Hidden except when the viewport is tilted (use the *View* > *Tilt* menu action). Indicates the current tilt angle of the viewport and allows precise values to be chosen.
|
||||
| Node Graph | Toggles the visibility of the overlaid node graph. |
|
||||
| Overlays | <p>When checked (default), overlays are shown. When unchecked, they are hidden. Overlays are the temporary contextual visualizations (like bounding boxes and vector manipulators) that are usually blue and appear atop the viewport when using tools.</p> |
|
||||
| Snapping | <p>When checked (default), drawing and dragging shapes and vector points means they will snap to other areas of geometric interest like corners or anchor points. When unchecked, the selection moves freely.<br /><br />Fine-grained options are available by clicking the overflow button to access its options popover menu:</p><p><img src="https://static.graphite.rs/content/learn/interface/document-panel/snapping-popover__3.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>Snapping options relating to **Bounding Boxes**:</p><p><ul><li>**Box Center** enables snapping to the center point of any layer's bounding box.</li><li>**Box Corner** enables snapping to the four corners of any layer's bounding box.</li><li>**Along Edge** enables snapping anywhere along the four edges of any layer's bounding box.</li><li>**Midpoint of Edge** enables snapping to any of the four points at the middle of the edges of any layer's bounding box.</li><li>**Align to Box** enables snapping to anywhere outside any layer's bounding box along the four lines extending outward from its edges.</li><li>**Evenly Distribute Boxes** enables snapping to a consistent distance offset established by the bounding boxes of nearby layers (due to a bug, **Box Center** and **Box Corner** must be enabled).</li></ul></p><p>Snapping options relating to **Geometry**:</p><p><ul><li>**Anchor** enables snapping to the anchor point of any vector path.</li><li>**Line Midpoint** enables snapping to the point at the middle of any straight line segment of a vector path.</li><li>**Path** enables snapping along the length of any vector path.</li><li>**Normal to Path** enables snapping a line to a point perpendicular to a vector path (due to a bug, **Intersection** must be enabled).</li><li>**Tangent to Path** enables snapping a line to a point tangent to a vector path (due to a bug, **Intersection** must be enabled).</li><li>**Intersection** enables snapping to any points where vector paths intersect.</li><li>**Align to Selected Path** enables snapping to the handle control points of a selected vector layer.</li></ul></p> |
|
||||
| Grid | <p>When checked (off by default), grid lines are shown and snapping to them becomes active. The initial grid scale is 1 document unit, helping you draw pixel-perfect artwork.</p><ul><li><p>**Type** sets whether the grid pattern is made of squares or triangles.</p><p>**Rectangular** is a pattern of horizontal and vertical lines:</p><p><img src="https://static.graphite.rs/content/learn/interface/document-panel/grid-rectangular-popover__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>It has one option unique to this mode:</p><ul><li>**Spacing** is the width and height of the rectangle grid cells.</li></ul><p>**Isometric** is a pattern of triangles:</p><p><img src="https://static.graphite.rs/content/learn/interface/document-panel/grid-isometric-popover__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Snapping options popover menu" /></p><p>It has two options unique to this mode:</p><ul><li>**Y Spacing** is the height between vertical repetitions of the grid.</li><li>**Angles** is the slant of the upward and downward sloped grid lines.</li></ul></li><li>**Display** gives control over the appearance of the grid. The **Display as dotted grid** checkbox (off by default) replaces the solid lines with dots at their intersection points.</li><li>**Origin** is the position in the canvas where the repeating grid pattern begins from. If you need an offset for the grid where an intersection occurs at a specific location, set those coordinates.</li></ul> |
|
||||
| View Mode | <p>**Normal** (default): The artwork is rendered normally.</p><p>**Outline**: The artwork is rendered as a wireframe.</p><p>**Pixels**: **Not implemented yet.** The artwork is rendered as it would appear when exported as a bitmap image at 100% scale regardless of the viewport zoom level.</p> |
|
||||
| Zoom In | <p>Zooms the viewport in to the next whole increment.</p> |
|
||||
| Zoom Out | <p>Zooms the viewport out to the next whole increment.</p> |
|
||||
| Reset Tilt and Zoom to 100% | <p>Resets the viewport tilt to 0°. Resets the viewport zoom to 100% which matches the canvas and viewport pixel scale 1:1.</p> |
|
||||
| Viewport Zoom | <p>Indicates the current zoom level of the viewport and allows precise values to be chosen.</p> |
|
||||
| Viewport Tilt | <p>Hidden except when the viewport is tilted (use the *View* > *Tilt* menu action). Indicates the current tilt angle of the viewport and allows precise values to be chosen.</p> |
|
||||
| Node Graph | <p>Toggles the visibility of the overlaid node graph.</p> |
|
||||
|
||||
### Shelf
|
||||
## Tool shelf
|
||||
|
||||
This narrow bar runs vertically down the left side of the Document panel beside the table where the viewport is displayed.
|
||||
This narrow bar runs vertically down the left side of the Document panel beside the viewport.
|
||||
|
||||
#### Tool shelf
|
||||
### Tools
|
||||
|
||||
Located at the top of the shelf area, the **tool shelf** provides a selection of **tools** for interactively editing the artwork.
|
||||
Located at the top of the tool shelf area, the **tool shelf** provides a selection of **tools** for interactively editing the artwork.
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| <img src="https://static.graphite.rs/content/learn/interface/document-panel/tool-shelf__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="The tool shelf" /> | The tool shelf is split into three sections: the **general tools** (gray icons), **vector tools** (blue icons), and **raster tools** (orange icons).<br /><br /><ul><li>**General tools** are used for assorted editing tasks within the viewport.</li><li>**Vector tools** are used for drawing and editing vector shapes, curves, and text.</li><li>**Raster tools** are used for drawing and editing raster image content. The grayed out icons are placeholders for upcoming tools.</li></ul> |
|
||||
| <img src="https://static.graphite.rs/content/learn/interface/document-panel/tool-shelf__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="The tool shelf" /> | <p>The tool shelf is split into three sections: the **general tools** (gray icons), **vector tools** (blue icons), and **raster tools** (orange icons).</p><p><ul><li>**General tools** are used for assorted editing tasks within the viewport.</li><li>**Vector tools** are used for drawing and editing vector shapes, paths, and text.</li><li>**Raster tools** are used for drawing and editing raster image content. The grayed out icons are placeholders for upcoming tools.</li></ul></p> |
|
||||
|
||||
#### Working colors
|
||||
### Working colors
|
||||
|
||||
The **working colors** are the two colors used by the active tool.
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| <img src="https://static.graphite.rs/content/learn/interface/document-panel/working-colors.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The working colors" /> | The upper circle is the **primary color**. The lower circle is the **secondary color**.<br /><br />There are two buttons located underneath: **Swap** which reverses the current color choices, and **Reset** which restores the primary color to black and the secondary color to white. |
|
||||
| <img src="https://static.graphite.rs/content/learn/interface/document-panel/working-colors.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The working colors" /> | <p>The upper circle is the **primary color**. The lower circle is the **secondary color**.</p><p>There are two buttons located underneath: **Swap** which reverses the current color choices, and **Reset** which restores the primary color to black and the secondary color to white.</p> |
|
||||
|
||||
Various tools provide choices for using the primary and secondary colors as controls in the tool options. For example, many vector tools have **Fill** and **Stroke** options that use the current secondary and primary colors, respectively, as defaults:
|
||||
The tool controls (above the viewport) for some of the tools offer choices for using the primary and secondary colors. For example, the vector drawing tools have **Fill** and **Stroke** options that use the current secondary and primary colors, respectively, as defaults:
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/document-panel/tool-options-fill-stroke-colors__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The Fill and Stroke controls for a vector tool's options" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/document-panel/tool-options-fill-stroke-colors__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="The Fill and Stroke controls for a vector tool's controls" /></p>
|
||||
|
||||
These options each allow choices of being driven by the primary working color, secondary working color, or a custom color set just for that tool.
|
||||
|
||||
### Table
|
||||
## Viewport
|
||||
|
||||
The **table** contains the **viewport** bounded by rulers and scrollbars along its edges.
|
||||
The **viewport** is the view into the canvas. It lets you interact visually with the artwork content. It also displays temporary contextual **overlays** atop the content to assist with your editing.
|
||||
|
||||
#### Rulers and scrollbars
|
||||
|
||||
The **rulers**, located along the top and left edges within the table, display the size and location of the viewport's visible region in canvas coordinates. The rulers can be hidden with the *View* > *Rulers* toggleable menu option.
|
||||
|
||||
The **scrollbars**, located along the bottom and right edges within the table, allow scrolling the artwork to show different parts of the canvas in the viewport.
|
||||
|
||||
#### Viewport
|
||||
|
||||
The **viewport** is the view into the canvas. It is where the artwork is displayed and gets interactively edited using the tools.
|
||||
|
||||
## Overlaid node graph editing
|
||||
|
||||
Opening the overlaid node graph shows the structure of nodes and layers that compose the document artwork. It's a more detailed view of what the [Layers](../layers-panel) and [Properties](../properties-panel) panels show.
|
||||
|
||||
**Nodes** are the entities with left-to-right input **connectors**.
|
||||
|
||||
**Layers** are the larger entities shown with thumbnails and a bottom-to-top direction of data stacking. Their purpose is to composite sources of graphical data on top of one another in a **layer stack**. Layers take input from other nodes or layers via a connector on their left side. When that connector is fed by another layer stack, the Layers panel considers it a **group** because it combines one stack into another parent stack.
|
||||
|
||||
Layers and nodes are wired together using **links** which send data between the outputs of nodes to the inputs of others. You can wire up a node by dragging from the output connector of one node to the input connector of its destination node. But note that forming cyclic graphs, where a loop can be traced along the links of a set of nodes, is not permitted. Graphical data flows into the **Output** node which then becomes rendered to the document viewport.
|
||||
|
||||
### Top bar
|
||||
|
||||
Provides several controls for the graph and selected node or layer. The options change based on what's selected.
|
||||
|
||||
#### Node/layer controls
|
||||
|
||||
When a layer or node is selected, these buttons will show up on the left side of the top bar:
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/document-panel/node-controls-buttons.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="The node/layer controls" /></p>
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| Make<span> </span>Hidden/<br />Make<span> </span>Visible | Toggles the visibility state of the layer or node. This is equivalent to the eye icon button displayed beside each layer. If a node or layer is hidden, it gets bypassed in the data flow. <kbd>Ctrl</kbd><kbd>H</kbd> (macOS: <kbd>⌘</kbd><kbd>H</kbd>) is a shortcut for this toggle that can be used from the graph or viewport. |
|
||||
| Preview/<br />End<span> </span>Preview | Temporarily moves the graph output away from the Output node and the graph output is instead provided by the previewed node. While previewing, the node is styled with a dashed, brighter border. Ending the preview returns responsibility back to the Output node. This is a handy feature for viewing part of a graph without needing to disconnect the actual Output node and manually restore it later. Clicking a node or layer in the graph while holding <kbd>Alt</kbd> is a shortcut for toggling its preview. |
|
||||
|
||||
### Shelf
|
||||
|
||||
This narrow bar, which is currently empty, runs vertically down the left side of the Document panel beside the graph area. In the future, icons for categories of nodes will be listed here, allowing quick access and browsing.
|
||||
|
||||
#### Working colors
|
||||
|
||||
Same functionality as [explained for the viewport](#working-colors) (when the graph overlay is closed).
|
||||
The viewport is also surrounded by **scrollbars** and **rulers** around its edges. Scrollbars, located along the bottom/right edges, allow scrolling the artwork to show different parts in the viewport. Rulers, located along the top/left edges, offer dimensional information for the area of the canvas that's within view. They can be hidden with the *View* > *Rulers* toggleable menu option.
|
||||
|
|
|
@ -21,93 +21,97 @@ The **app button** shows the [Graphite logo](/logo). Clicking it opens the Graph
|
|||
|
||||
## File
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-file.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The File menu" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-file__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The File menu" /></p>
|
||||
|
||||
The **File menu** lists actions related to file handling:
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| New… | Opens the **New Document** dialog for creating a blank canvas in a new editor tab.<br /><br /><img src="https://static.graphite.rs/content/learn/interface/menu-bar/dialog-new-document.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The 'New Document' dialog" /> |
|
||||
| Open… | Opens the operating system file picker dialog for selecting a `.graphite` file from disk to be opened in a new editor tab. |
|
||||
| Open Demo Artwork… | Opens the **Demo Artwork** dialog for loading a choice of premade sample artwork files provided for you to explore. Click the button below each image to open it.<br /><br /><img src="https://static.graphite.rs/content/learn/interface/menu-bar/dialog-demo-artwork.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The 'Demo Artwork' dialog" /> |
|
||||
| Close | Closes the active document. If it has unsaved changes (denoted by the `*` after the file name), you will be asked to save or discard the changes. |
|
||||
| Close All | Closes all open documents. To avoid accidentally losing unsaved work, you will be asked to confirm that you want to proceed which will discard the unsaved changes in all open documents. |
|
||||
| Save | Saves the active document by writing the `.graphite` file to disk. An operating system file download dialog may appear asking where to place it. That dialog will provide an opportunity to save over a previous version of the file, if you wish, by picking the identical name instead of saving another instance with a number after it. |
|
||||
| Import… | Opens the operating system file picker dialog for selecting an image file from disk to be placed as a new bitmap image layer into the active document. |
|
||||
| Export… | Opens the **Export** dialog for saving the artwork as a *File Type* of *PNG*, *JPG*, or *SVG*. *Scale Factor* multiplies the content's document scale, so a value of 2 would export 300x400 content as 600x800 pixels. *Bounds* picks what area to render: *All Artwork* uses the bounding box of all layers, *Selection* uses the bounding box of the currently selected layers, and an *Artboard: \[Name\]* uses the bounds of that artboard. *Transparency* exports the PNG or SVG file with transparency instead of the artboard background color.<br /><br /><img src="https://static.graphite.rs/content/learn/interface/menu-bar/dialog-export.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The 'Export' dialog" /> |
|
||||
| Preferences… | Opens the **Editor Preferences** dialog for configuring Graphite's settings.<br /><br /><img src="https://static.graphite.rs/content/learn/interface/menu-bar/dialog-editor-preferences.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The 'Editor Preferences' dialog" /> |
|
||||
| New… | <p>Opens the **New Document** dialog for creating a blank canvas in a new editor tab.</p><p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/dialog-new-document.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The 'New Document' dialog" /></p> |
|
||||
| Open… | <p>Opens the operating system file picker dialog for selecting a `.graphite` file from disk to be opened in a new editor tab.</p> |
|
||||
| Open Demo Artwork… | <p>Opens the **Demo Artwork** dialog for loading a choice of premade sample artwork files provided for you to explore. Click the button below each image to open it.</p><p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/dialog-demo-artwork__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The 'Demo Artwork' dialog" /></p> |
|
||||
| Close | <p>Closes the active document. If it has unsaved changes (denoted by the `*` after the file name), you will be asked to save or discard the changes.</p> |
|
||||
| Close All | <p>Closes all open documents. To avoid accidentally losing unsaved work, you will be asked to confirm that you want to proceed which will discard the unsaved changes in all open documents.</p> |
|
||||
| Save | <p>Saves the active document by writing the `.graphite` file to disk. An operating system file download dialog may appear asking where to place it. That dialog will provide an opportunity to save over a previous version of the file, if you wish, by picking the identical name instead of saving another instance with a number after it.</p> |
|
||||
| Import… | <p>Opens the operating system file picker dialog for selecting an image file from disk to be placed as a new bitmap image layer or SVG content into the active document.</p> |
|
||||
| Export… | <p>Opens the **Export** dialog for saving the artwork as a *File Type* of *PNG*, *JPG*, or *SVG*. *Scale Factor* multiplies the content's document scale, so a value of 2 would export 300x400 content as 600x800 pixels. *Bounds* picks what area to render: *All Artwork* uses the bounding box of all layers, *Selection* uses the bounding box of the currently selected layers, and an *Artboard: \[Name\]* uses the bounds of that artboard. *Transparency* exports PNG or SVG files with transparency instead of the artboard background color.<br /><br /><img src="https://static.graphite.rs/content/learn/interface/menu-bar/dialog-export.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The 'Export' dialog" /></p> |
|
||||
| Preferences… | <p>Opens the **Editor Preferences** dialog for configuring Graphite's settings.<br /><br /><img src="https://static.graphite.rs/content/learn/interface/menu-bar/dialog-editor-preferences__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The 'Editor Preferences' dialog" /></p> |
|
||||
|
||||
## Edit
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-edit.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The Edit menu" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-edit__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The Edit menu" /></p>
|
||||
|
||||
The **Edit menu** lists actions related to the editing workflow:
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| Undo | Steps back in the history of changes in the active document. |
|
||||
| Redo | Steps forward in the history of changes in the active document. |
|
||||
| Cut | Copies the selected layer(s) to the clipboard, then deletes them. |
|
||||
| Copy | Copies the selected layer(s) to the clipboard. |
|
||||
| Paste | Pastes the copied layer(s) from the clipboard into the document. It will end up beside a selected layer or inside a selected folder, or otherwise at the base of the folder structure.<br /><br />In the web version of Graphite, your browser will ask for permission to read from your clipboard which you must grant; using the hotkey <kbd>Ctrl</kbd><kbd>V</kbd> (macOS: <kbd>⌘</kbd><kbd>V</kbd>) works without browser permission. |
|
||||
| Undo | <p>Steps back in the history of changes in the active document.</p> |
|
||||
| Redo | <p>Steps forward in the history of changes in the active document.</p> |
|
||||
| Cut | <p>Copies the selected layer(s) to the clipboard, then deletes them.</p> |
|
||||
| Copy | <p>Copies the selected layer(s) to the clipboard.</p> |
|
||||
| Paste | <p>Pastes the copied layer(s) from the clipboard into the document. It will end up directly above the selected layer, or otherwise at the base of the folder structure.</p><p>In the web version of Graphite, your browser will ask for permission to read from your clipboard which you must grant; alternatively, using the hotkey <kbd>Ctrl</kbd><kbd>V</kbd> (macOS: <kbd>⌘</kbd><kbd>V</kbd>) works without the browser needing this permission.</p> |
|
||||
|
||||
## Layer
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-layer.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The Layer menu" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-layer__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The Layer menu" /></p>
|
||||
|
||||
The **Layer menu** lists actions related to the layers within a document:
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| Select All | Selects all layers and folders in the document. |
|
||||
| Deselect All | Deselects everything in the document. |
|
||||
| Delete Selected | Removes all selected layers and folders. |
|
||||
| Grab Selected | Begin grabbing the selected layer(s) to translate (move) them around with your cursor's movement. Lock to an axis with <kbd>X</kbd> or <kbd>Y</kbd> then use the number keys to type a pixel distance value. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>. |
|
||||
| Rotate Selected | Begin rotating the selected layer(s) around their pivot point with your cursor's movement. Use the number keys to type an angle value in degrees. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>. |
|
||||
| Scale Selected | Begin scaling the selected layer(s) around their pivot point with your cursor's movement. Lock to an axis with <kbd>X</kbd> or <kbd>Y</kbd>. Use the number keys to type a scale multiplier value. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>. |
|
||||
| Order ><br />Raise to Front | Reorders the selected layer(s) above all other layers within their same folder(s), so they appear in the layer stack and render above those other layers. |
|
||||
| Order ><br />Raise | Reorders the selected layers(s) up by one in the layer stack, so any layer that was immediately above the selected layer(s) ends up immediately below. |
|
||||
| Order ><br />Lower | Reorders the selected layers(s) down by one in the layer stack, so any layer that was immediately below the selected layer(s) ends up immediately above. |
|
||||
| Order ><br />Lower to Back | Reorders the selected layer(s) below all other layers within their same folder(s), so they appear in the layer stack and render below those other layers. |
|
||||
| New | <p>Creates a new layer in the active document. It will end up directly above the selected layer, or otherwise at the base of the folder structure.</p> |
|
||||
| Select All | <p>Selects all layers and folders in the document.</p> |
|
||||
| Deselect All | <p>Deselects everything in the document.</p> |
|
||||
| Previous Selection | <p>Goes back to the previously selected set of layers/nodes in the selection history.</p><p>If the side of your mouse has navigation buttons, you can use the back button as a shortcut (not supported in Firefox).</p> |
|
||||
| Next Selection | <p>Goes forward to the next selected set of layers/nodes in the selection history.</p><p>If the side of your mouse has navigation buttons, you can use the forward button as a shortcut (not supported in Firefox).</p> |
|
||||
| Group Selected | <p>Creates a new folder in place of the selected layer(s), then moves them into that folder.</p> |
|
||||
| Delete Selected | <p>Removes all selected layers and folders.</p> |
|
||||
| Grab Selected | <p>Begin grabbing the selected layer(s) to translate (move) them around with your cursor's movement. Lock to an axis with <kbd>X</kbd> or <kbd>Y</kbd> then use the number keys to type a pixel distance value. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>.</p> |
|
||||
| Rotate Selected | <p>Begin rotating the selected layer(s) around their pivot point with your cursor's movement. Use the number keys to type an angle value in degrees. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>.</p> |
|
||||
| Scale Selected | <p>Begin scaling the selected layer(s) around their pivot point with your cursor's movement. Lock to an axis with <kbd>X</kbd> or <kbd>Y</kbd>. Use the number keys to type a scale multiplier value. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>.</p> |
|
||||
| Order ><br />Raise to Front | <p>Reorders the selected layer(s) above all others within their same folder(s), so they appear in the layer stack and render above those other layers.</p> |
|
||||
| Order ><br />Raise | <p>Reorders the selected layers(s) up by one in the layer stack, so any layer that was immediately above the selected layer(s) ends up immediately below.</p> |
|
||||
| Order ><br />Lower | <p>Reorders the selected layers(s) down by one in the layer stack, so any layer that was immediately below the selected layer(s) ends up immediately above.</p> |
|
||||
| Order ><br />Lower to Back | <p>Reorders the selected layer(s) below all others within their same folder(s), so they appear in the layer stack and render below those other layers.</p> |
|
||||
|
||||
## Document
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-document.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The Document menu" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-document__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The Document menu" /></p>
|
||||
|
||||
The **Document menu** lists actions related to the document and artwork:
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| Clear Artboards | Removes all artboards from the document, thus enabling an infinite canvas. |
|
||||
| Clear Artboards | <p>Removes all artboards from the document, resulting in an infinite canvas.</p> |
|
||||
|
||||
## View
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-view.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The View menu" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-view__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The View menu" /></p>
|
||||
|
||||
The **View menu** lists actions related to the view of the canvas and viewport:
|
||||
The **View menu** lists actions related to the view of the canvas within the viewport:
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| Tilt | Begins tilting the viewport angle based on your mouse movements. Hold <kbd>Ctrl</kbd> to snap to 15° increments. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>. |
|
||||
| Reset Tilt | Sets the viewport tilt angle back to 0°. |
|
||||
| Zoom In | Narrows the view to the next whole zoom increment. |
|
||||
| Zoom Out | Widens the view to the next whole zoom increment. |
|
||||
| Zoom to Fit Selection | Zooms and frames the viewport to the bounding box of the selected layer(s). |
|
||||
| Zoom to Fit All | Zooms and frames the viewport to fit all artboards, or all artwork if using infinite canvas. |
|
||||
| Zoom to 100% | Zooms the viewport in or out to 100% scale, matching 1:1 the scale of the document and viewport. |
|
||||
| Zoom to 200% | Zooms the viewport in or out to 200% scale, displaying the artwork at twice the actual size. |
|
||||
| Rulers | Toggles visibility of the rulers shown along the top and left edges of the viewport. |
|
||||
| Tilt | <p>Begins tilting the viewport angle based on your mouse movements.</p><p>While tilting, hold <kbd>Ctrl</kbd> to snap to 15° increments. Confirm with a left click or <kbd>Enter</kbd>. Cancel with a right click or <kbd>Esc</kbd>.</p> |
|
||||
| Reset Tilt | <p>Sets the viewport tilt angle back to 0°.</p> |
|
||||
| Zoom In | <p>Narrows the view to the next whole zoom increment, such as:</p><p>25%, 33.33%, 40%, 50%, 66.67%, 80%, 100%, 125%, 160%, 200%, 250%, 320%, 400%, 500%</p> |
|
||||
| Zoom Out | <p>Widens the view to the next whole zoom increment, such as above.</p> |
|
||||
| Zoom to Fit Selection | <p>Zooms and frames the viewport to the bounding box of the selected layer(s).</p> |
|
||||
| Zoom to Fit All | <p>Zooms and frames the viewport to fit all artboards, or all artwork if using infinite canvas.</p> |
|
||||
| Zoom to 100% | <p>Zooms the viewport in or out to 100% scale, making the document and viewport scales match 1:1.</p> |
|
||||
| Zoom to 200% | <p>Zooms the viewport in or out to 200% scale, displaying the artwork at twice the actual size.</p> |
|
||||
| Rulers | <p>Toggles visibility of the rulers along the top/left edges of the viewport.</p> |
|
||||
|
||||
## Help
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-help.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The Help menu" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/interface/menu-bar/menu-help__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="The Help menu" /></p>
|
||||
|
||||
The **Help menu** lists actions related to information about Graphite:
|
||||
|
||||
| | |
|
||||
|-|-|
|
||||
| About Graphite… | Opens the **About Graphite** dialog for displaying release and license information. |
|
||||
| User Manual | Opens this [user manual](./learn). |
|
||||
| Report a Bug | Opens a page to file a [new GitHub issue](https://github.com/GraphiteEditor/Graphite/issues/new). |
|
||||
| Visit on GitHub | Opens the [Graphite GitHub repository](https://github.com/GraphiteEditor/Graphite). |
|
||||
| *Debug section* | Developer-only actions. |
|
||||
| About Graphite… | <p>Opens the **About Graphite** dialog for displaying release and license information. You can check it for the release date of the current editor version.</p> |
|
||||
| User Manual | <p>Opens this [user manual](./learn).</p> |
|
||||
| Report a Bug | <p>Opens a page to file a [new GitHub issue](https://github.com/GraphiteEditor/Graphite/issues/new).</p> |
|
||||
| Visit on GitHub | <p>Opens the [Graphite GitHub repository](https://github.com/GraphiteEditor/Graphite).</p> |
|
||||
| *Debug section* | <p>Developer-only actions. Users should ignore these.</p> |
|
||||
|
|
|
@ -5,14 +5,14 @@ page_template = "book.html"
|
|||
|
||||
[extra]
|
||||
order = 1
|
||||
js = ["video-embed.js"]
|
||||
js = ["youtube-embed.js"]
|
||||
+++
|
||||
|
||||
<!-- Before taking the time to read the coming chapters, let's build some context by jumping straight into a small project that you can follow along with. That way you will have a mental framework for the topics explained in the rest of this manual. -->
|
||||
|
||||
Begin learning Graphite by watching the introductory tutorial videos below.
|
||||
|
||||
One is available now, and more will be released on a regular basis throughout early 2024.
|
||||
One is available now, and more will be released on a regular basis throughout early 2025.
|
||||
|
||||
## Tutorial series
|
||||
|
||||
|
|
|
@ -5,39 +5,31 @@ title = "Features and limitations"
|
|||
order = 1
|
||||
+++
|
||||
|
||||
Please bear in mind that Graphite is alpha software, meaning it is actively changing and improving. Remember to save your work frequently because crashes are not unheard of.
|
||||
Bear in mind that Graphite is alpha software, meaning it is actively changing and improving.
|
||||
|
||||
## Current capabilities
|
||||
|
||||
A lot is planned on the future [roadmap](/features#roadmap), but here's an overview of the concepts behind the selection of workflows you can currently use in Graphite.
|
||||
|
||||
### Vector illustration and graphic design
|
||||
|
||||
Vector editing is the core competency of the Graphite editor at this stage in its development. That means you can create graphic designs and shape-based vector artwork with the tools on offer, like this cactus:
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/index/just-a-potted-cactus-thumbnail.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Example vector artwork of a potted cactus" /></p>
|
||||
|
||||
Primitive geometry like rectangles and ellipses can be drawn and, as desired, modified into more complex shapes using the Path tool. Fully organic shapes may also be created from scratch with the Pen tool. They can then be given colors and gradients to add visual style.
|
||||
|
||||
### Raster compositing
|
||||
|
||||
Raster image editing is a growing capability that will develop over time into the central focus of Graphite. Raster imagery is composed of pixels which are grids of color that can represent anything visual, like paintings and photographs. The current feature set lets you import images, manipulate them using the node-based compositor, and apply nondestructive effects like color adjustment filters.
|
||||
|
||||
A prototype Brush tool exists letting you draw simple doodles and sketches. However it is very limited in its capabilities and there are multiple bugs and performance issues with the feature. It can be used in a limited capacity, but don't expect to paint anything too impressive using raster brushes quite yet.
|
||||
|
||||
The raster-based Imaginate feature enables you to synthesize artwork using generative AI based on text descriptions. With it, you can also nondestructively modify your vector art and imported images. You can inpaint (or outpaint) the content in a specific masked part of an image or use it to touch up quick-and-dirty compositions. This feature is temporarily out of comission but will be resurrected, and further improved upon, in the near future.
|
||||
| | |
|
||||
|-|-|
|
||||
| <p>Vector editing is the core competency of the Graphite editor at this stage in its development. That means you can create shape-based vector artwork and designs with the available tools.</p><p>Primitive geometry like rectangles and ellipses can be drawn and, as desired, modified into more complex shapes using the Path tool. Fully organic shapes may also be created from scratch with the Pen tool. They can then be given colors and gradients to add visual style. This cactus is an example of the style of artwork you can create with vector graphics.</p> | <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/cactus-vector-art.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Example vector artwork of a potted cactus" style="max-width: unset" /> |
|
||||
|
||||
### Procedural design
|
||||
|
||||
A procedural content generation workflow lets you describe *how* a creative decision becomes a visual outcome rather than doing it all yourself. For example, copying a shape 25 times around the inside of a circle would be a lot of work if done by hand but it's easy for the computer to do it. And if you decide 10 instances may look better than 25, or you want to change the copied shape, or you opt for a different circle radius, it's easy to just update a numerical parameter. That saves you from laboriously placing every shape all over again. You're able to build a *procedure* that the computer carries out on your behalf.
|
||||
A procedural content generation workflow lets you describe *how* a creative decision becomes a visual outcome rather than doing it all yourself. For example, copying a shape 25 times around the inside of a circle would be tedious work if done by hand but it's easy for the computer to do it. And if you decide 10 instances may look better than 25, or you want to change the copied shape, or you opt for a different radial separation, it's easy to just update a numerical parameter. That saves you from laboriously placing every shape all over again. You're able to build a *procedure* that the computer carries out on your behalf.
|
||||
|
||||
The aforementioned example takes the form of the <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" style="vertical-align: middle" alt="Circular Repeat" /> node which is represented as this box-shaped entity with colored *connectors* on either end. *Nodes* encode certain operations (or functions) in the procedure that generates your artwork. Once you've drawn some content, you can see the nodes which generate it by opening the *node graph* with the <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/node-graph-button.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" style="vertical-align: middle" alt="'Node Graph' button" /> button located to the top right of the viewport. This example may have a node setup which looks like this:
|
||||
The aforementioned example takes the form of the <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node__3.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" style="vertical-align: middle" alt="Circular Repeat" /> node which is represented as this box-shaped entity with colored *connectors* on either end. *Nodes* encode certain operations (or functions) in the procedure that generates your artwork. Once you've drawn some content, you can see the nodes which generate it by opening the *node graph* with the <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/node-graph-button.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" style="vertical-align: middle" alt="'Node Graph' button" /> button located to the top right of the viewport. This example may have a node setup which looks like this:
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/shape-fill-circular-repeat-layer.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Shape" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/path-fill-circular-repeat-layer.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Path node, Fill node, Circular Repeat node in a sequence feeding into the Untitled Layer" /></p>
|
||||
|
||||
Starting from the left, the <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/shape-node.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" style="vertical-align: middle" alt="Shape" /> node generates some geometry (in this case, drawn using the *Pen* tool). Next, the shape data feeds through the <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/fill-node.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" style="vertical-align: middle" alt="Fill" /> node to apply a blue color. At this point, the shape data looks like so:
|
||||
Starting from the left, the <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/path-node.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" style="vertical-align: middle" alt="Path" /> node generates some geometry (in this case, drawn using the *Pen* tool). Next, the vector path data feeds through the <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/fill-node__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" style="vertical-align: middle" alt="Fill" /> node to apply a blue color. At this point, the path data looks like so:
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/blue-arch-shape.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="" /></p>
|
||||
<p><img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/blue-arch-shape.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="" /></p>
|
||||
|
||||
Next, that is fed into the <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" style="vertical-align: middle" alt="Circular Repeat" /> node which has several parameters you can modify and get different output data based on your choices, like in these examples:
|
||||
Next, that is fed into the <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node__3.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" style="vertical-align: middle" alt="Circular Repeat" /> node which has several parameters you can modify and get different output data based on your choices, like in these examples:
|
||||
|
||||
<style class="table-1-style">
|
||||
.table-1-style + table {
|
||||
|
@ -52,12 +44,20 @@ Next, that is fed into the <img src="https://static.graphite.rs/content/learn/in
|
|||
|
||||
| | |
|
||||
|-|-|
|
||||
| <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-1.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="" /> | <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-output-1.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="" /> |
|
||||
| <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="" /> | <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-output-2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="" /> |
|
||||
| <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-3.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="" /> | <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-output-3.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="" /> |
|
||||
| <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-4.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="" /> | <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-output-4.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width /= 2" alt="" /> |
|
||||
| <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-1__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="" /> | <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-output-1.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="" /> |
|
||||
| <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-2__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="" /> | <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-output-2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="" /> |
|
||||
| <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-3__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="" /> | <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-output-3.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="" /> |
|
||||
| <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-parameters-4__2.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="" /> | <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/circular-repeat-node-output-4.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="" /> |
|
||||
|
||||
The node's properties offer controls over settings like *Angle Offset* (what angle to start at), *Radius* (distance from the center), and *Count* (how many copies to distribute). These parameters can also be exposed into the graph so they are driven by the calculated numerical outputs of other nodes instead of values you pick by hand.
|
||||
The node's properties offer controls over settings like *Angle Offset* (what angle to start at), *Radius* (distance from the center), and *Instances* (how many copies to distribute). These parameters can also be exposed into the graph so they are driven by the calculated numerical outputs of other nodes instead of values you pick by hand.
|
||||
|
||||
### Raster compositing
|
||||
|
||||
Raster image editing is a growing capability that will develop over time into the central focus of Graphite. Raster imagery is composed of pixels which are grids of color that can represent anything visual, like paintings and photographs. The current feature set lets you import images, manipulate them using the node-based compositor, and apply nondestructive global effects like color adjustment filters.
|
||||
|
||||
A prototype <img src="https://static.graphite.rs/content/learn/introduction/features-and-limitations/brush-tool-icon.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="" style="vertical-align: bottom" /> Brush tool exists letting you draw simple doodles and sketches. However it is very limited in its capabilities and there are multiple bugs and performance issues with the feature. It can be used in a basic capacity, but don't expect to paint anything too impressive using raster brushes quite yet. The tool will be fully rewritten in the future.
|
||||
|
||||
<!-- The raster-based Imaginate feature enables you to synthesize artwork using generative AI based on text descriptions. With it, you can also nondestructively modify your vector art and imported images. You can inpaint (or outpaint) the content in a specific masked part of an image or use it to touch up quick-and-dirty compositions. This feature is temporarily out of comission but will be resurrected, and further improved upon, in the near future. -->
|
||||
|
||||
## Status and limitations
|
||||
|
||||
|
@ -67,16 +67,22 @@ Please make yourself aware of these factors to better understand and work around
|
|||
|
||||
Saved documents will eventually fail to render in future versions of the Graphite editor because of code changes. Since node implementations and other systems are in flux, file format stability isn't possible yet during this alpha stage of development. If a file opens but there's a rendering error, you may need to open the node graph and replace outdated nodes by creating new ones near the site of an error. Later in the development roadmap, a redesigned file format with a `.gdd` (Graphite Design Document) extension will replace `.graphite` files and it will be built with seamless backwards-compatability in mind.
|
||||
|
||||
### Limited raster capabilities
|
||||
### Limited raster tooling
|
||||
|
||||
While you can import bitmap images, apply image effects in the node graph, and draw brush strokes, there is not much tooling yet to make the overall raster workflow that useful. Marquee selection is an upcoming feature in the first half of 2025 which will significantly improve the utility of raster editing in Graphite. Hardware accelerated rendering, to offload work from the CPU to GPU, is also planned for early 2025 which will drastically improve the performance of working with millions of pixels.
|
||||
While you can import bitmap images, apply image effects in the node graph, and draw brush strokes, there is not much tooling yet to make the overall raster workflow that useful. Marquee selection is an upcoming feature slated for later in 2025 which will significantly improve the utility of raster editing in Graphite.
|
||||
|
||||
Hardware accelerated rendering, to offload pixel processing from the CPU to GPU, is also planned for 2025. It will drastically improve the performance of working with millions of pixels.
|
||||
|
||||
### Performance bottlenecks
|
||||
|
||||
Graphite has several temporary performance bottlenecks that currently yield poor performance when working with raster content, complex vector artwork, and large procedural node graphs. Each of these limitations will be resolved by finishing the implementations of the incomplete systems that impose slowdowns in their current forms. For example, caching in the node graph isn't operational and GPU-accelerated rendering isn't enabled yet. This will be a central focus throughout 2024 and 2025.
|
||||
Graphite has several temporary performance bottlenecks that currently yield poor responsiveness when working with raster content, complex vector artwork, and large procedural node graphs. This is especially impactful for raster content. It also currently applies to large volumes of vector data, such as paragraphs worth of text (which is represented as vector paths).
|
||||
|
||||
Each of these limitations will be resolved by finishing the implementations of incomplete systems that impose slowdowns in their current forms. For example, certain opportunities for node graph caching are not operational and GPU-accelerated rendering isn't enabled yet.
|
||||
|
||||
Performance will be a high-priority focus throughout 2025.
|
||||
|
||||
### Best-effort Safari support
|
||||
|
||||
Old versions of Safari lack the minimum web standards features Graphite requires to run. The latest version of the browser still won't run Graphite as well as Chrome and you may encounter extra bugs because we have limited resources to regularly test for Safari issues.
|
||||
Old versions of Safari lack support for the web standards Graphite is built upon. The latest version of the browser still won't run Graphite as well as Chrome and you may encounter extra bugs because we have limited resources to regularly test for Safari issues. Feel free to file issues only if you're using the latest Safari version and find a bug that isn't present in Chrome.
|
||||
|
||||
The latest Chrome or Chromium-based browser is recommended for the best-supported experience, although Firefox works with only some minor feature degradations.
|
||||
The latest Chrome, Edge, or Opera browser is recommended for the best-supported experience. Firefox works reasonably well, with only some minor loss of quality-of-life features. Brave is likely to encounter issues due to its aggressive degradation of web standards.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
title = "Graphite logo"
|
||||
|
||||
[extra]
|
||||
css = ["logo.css"]
|
||||
css_inline = ["logo.css"]
|
||||
+++
|
||||
|
||||
<section class="reading-material">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
title = "Volunteer"
|
||||
|
||||
[extra]
|
||||
css = ["volunteer.css"]
|
||||
css_inline = ["volunteer.css"]
|
||||
css_external = ["https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap"]
|
||||
+++
|
||||
|
||||
|
@ -43,7 +43,7 @@ The Graphite editor is built much like a game engine, split across user interfac
|
|||
</div>
|
||||
<div class="block feature-box-narrow">
|
||||
|
||||
<h1 class="feature-box-header">Graphene team</h1>
|
||||
<h1 class="feature-box-header">Compiler team</h1>
|
||||
|
||||
[Graphene](/volunteer/guide/graphene) is a programming language, interpreter, and runtime environment built upon Rust which enables Graphite artwork to compile to executable programs for fast rendering.
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ css_external = ["https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&displ
|
|||
|
||||
Welcome, potential contributor! We're excited to have you join the Graphite project and it's our goal to make the process as smooth as possible. This guide will serve as your library of knowledge to help you get started contributing to the project. If you find any information missing or unclear, please let us know through Discord or submit a pull request to help document the process for future contributors.
|
||||
|
||||
The next page will cover how to compile the Graphite source code. But first, make sure you've joined our [Discord server](https://discord.graphite.rs) and assigned yourself the *"<span class="emoji">🤖</span> Interested in contributing code"* role from the *#welcome* channel. Done that? Alright, proceed!
|
||||
The next page will cover how to compile the Graphite source code. But first, make sure you've joined our [Discord server](https://discord.graphite.rs) and assigned yourself the *"<span class="emoji">🤖</span> Interested in contributing code"* role from the `#🙂welcome` channel. Done that? Alright, proceed!
|
||||
|
||||
<p>
|
||||
<img src="https://static.graphite.rs/content/volunteer/code-contributions.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.jpg')" alt="Flavor graphic depicting a library of knowledge in a digital realm" />
|
||||
|
|
|
@ -29,13 +29,13 @@ The bar at the top of a _panel group_ which includes a clickable tab for each pa
|
|||
### Active tab
|
||||
The one tab in a _tab bar_ that is currently active. The user can click any inactive tab to make it become the active tab. The active tab shows the _panel content_ beneath it unless it is a _folded panel_.
|
||||
### Folded panel
|
||||
A shrunken _panel_ showing only the _tab bar_. A _panel_ consists of the _tab bar_ and _panel body_ except when the latter is folded away. The user may click the _active tab_ to fold and restore a panel, however a panel cannot be folded if there are no other unfolded panels in its column.
|
||||
A shrunken _panel_ showing only the _tab bar_. A _panel_ consists of the _tab bar_ and _panel content_ except when the latter is folded away. The user may click the _active tab_ to fold and restore a panel, however a panel cannot be folded if there are no other unfolded panels in its column.
|
||||
### Panel
|
||||
### Panel body
|
||||
### Options bar
|
||||
The bar that spans horizontally across the top of a _panel_ (located under the _tab bar_) which displays options related to the _panel_.
|
||||
### Panel content
|
||||
### Control bar
|
||||
The bar that spans horizontally across the top of a _panel_ (located under the _tab bar_) which displays controls related to the _panel_.
|
||||
### Viewport
|
||||
The area that takes up the main space in a _panel_ (located beneath the _options bar_) which displays the primary content of the _panel_.
|
||||
The area that takes up the main space in a _panel_ (located beneath the _control bar_) which displays the primary content of the _panel_.
|
||||
### Shelf
|
||||
The bar that spans vertically along the left side of some _panels_ (located left of the _viewport_) which displays a catalog of available items, such as document editing _tools_ or common _nodes_.
|
||||
### Tool
|
|
@ -13,7 +13,7 @@ order = 1 # Page number after chapter intro
|
|||
- Document title
|
||||
- Window buttons
|
||||
- Workspace
|
||||
- Panel interface (tab, pin, options bar, left menu)
|
||||
- Panel interface (tab, pin, control bar, left menu)
|
||||
- Arrangement and docking
|
||||
- Status bar
|
||||
- Multiple windows
|
||||
|
@ -22,7 +22,7 @@ order = 1 # Page number after chapter intro
|
|||
- Canvas and frames
|
||||
- Rulers
|
||||
- Tool menu
|
||||
- Options bar
|
||||
- Control bar
|
||||
- Properties
|
||||
- Blending
|
||||
- Origin
|
|
@ -0,0 +1,20 @@
|
|||
+++
|
||||
title = "Knowing your tooling"
|
||||
|
||||
[extra]
|
||||
order = 2 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
## First time builds
|
||||
|
||||
Slower.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Delete the `target`, `pkg`, `node_modules`, and `dist` directories.
|
||||
|
||||
## Slow builds
|
||||
|
||||
If you're seeing the terminal spend several seconds installing wasm-opt every recompilation, reinstall the exact version of `wasm-bindgen-cli` that matches the `wasm-bindgen` dependency in [`Cargo.toml`](https://github.com/GraphiteEditor/Graphite/blob/master/Cargo.toml).
|
||||
|
||||
## Rust Analyzer speed tips
|
|
@ -5,13 +5,219 @@ page_template = "book.html"
|
|||
|
||||
[extra]
|
||||
order = 2 # Chapter number
|
||||
js = ["video-embed.js"]
|
||||
js = ["youtube-embed.js"]
|
||||
+++
|
||||
|
||||
The best introduction for getting up-to-speed with Graphite contribution comes from watching this webcast recording. Before asking questions in Discord, please watch the full video because it gives a comprehensive overview of most things you will need to know.
|
||||
|
||||
<div class="video-embed aspect-16x9">
|
||||
<img data-video-embed="vUzIeg8frh4" src="https://static.graphite.rs/content/volunteer/guide/workshop-intro-to-coding-for-graphite-youtube.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Workshop: Intro to Coding for Graphite" />
|
||||
</div>
|
||||
|
||||
The Graphite editor is built as a web app powered by Svelte and TypeScript in the frontend and Rust in the backend which is compiled to WebAssembly and run in the browser. The editor makes calls into Graphene, the node graph engine which manages and renders the documents.
|
||||
<!-- ## Tech stack -->
|
||||
<!-- - rustc: Compiler for node graph generics and custom nodes -->
|
||||
<!-- - rust-gpu: Compiler backend to generate compute shaders from Rust source code -->
|
||||
<!-- - wgpu: Portable graphics API for running compute shaders on desktop and web -->
|
||||
<!-- - Tauri: lightweight desktop web UI shell while the backend runs natively (experimental) -->
|
||||
<!-- - Vello: GPU-accelerated vector graphics renderer -->
|
||||
<!-- - COSMIC Text: Text shaping and typesetting -->
|
||||
<!-- - Wasmer or Wasmtime: Portable, sandboxed runtime for custom nodes -->
|
||||
<!-- - Tokio: parallelized job execution in the node graph pipeline -->
|
||||
<!-- - Xilem: High-performance native UI framework, to replace Tauri when ready -->
|
||||
|
||||
The Editor's frontend web code lives in `/frontend/src`. The backend Rust code is located in `/editor`. Graphene is found in `/node-graph`.
|
||||
## Codebase structure
|
||||
|
||||
Graphite is built from several main software components. New developers may choose to specialize in one or more area without having to attain a working knowledge of the full codebase.
|
||||
|
||||
### Frontend
|
||||
|
||||
*Location: `/frontend/src`*
|
||||
|
||||
The frontend is the interface for Graphite which users see and interact with. It is built using web technologies with TypeScript and Svelte (HTML and SCSS). The frontend's philosophy is to be as lightweight and minimal as possible. It acts as the entry point for user input and then quickly hands off its work to the WebAssembly editor backend via its Wasm wrapper API. That API is written in Rust but has TypeScript bindings generated by the [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) tooling that is part of the Vite-based build chain. The frontend is built of many components that recursively form the window, panels, and widgets that make up the user interface.
|
||||
|
||||
### Editor
|
||||
|
||||
*Location: `/editor`*
|
||||
|
||||
The editor is the core of the Graphite application, and it's where all the business logic occurs for the tooling and user interaction. It is written in Rust and compiled to WebAssembly. At its heart is the message system described below. It is responsible for communicating with Graphene as well as handling the actual logic, state, tooling, and responsibilities of the interactive application.
|
||||
|
||||
### Graphene
|
||||
|
||||
*Location: `/node-graph`*
|
||||
|
||||
[Graphene](../graphene/) is the node graph engine which manages and renders the documents. It is itself a programming language, where Graphene programs are compiled while being edited live by the user, and where executing the program renders the document.
|
||||
|
||||
## Frontend/backend communication
|
||||
|
||||
Frontend-to-backend communication is achieved through a thin Rust translation layer in `/frontend/wasm/src/editor_api.rs` which wraps the editor backend's Rust-based message system API and provides the TypeScript-compatible API of callable functions. These wrapper functions are compiled by [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) into autogenerated TS functions that serve as an entry point from TS into the Wasm binary.
|
||||
|
||||
Backend-to-frontend communication happens by sending a queue of messages to the frontend message dispatcher. After the TS has called any wrapper API function to get into backend code execution, the editor's business logic runs and queues up `FrontendMessage`s (defined in `/editor/src/messages/frontend/frontend_message.rs`) which get mapped from Rust to TS-friendly data types in `/frontend/src/wasm-communication/messages.ts`. Various TS code subscribes to these messages by calling `subscribeJsMessage(MessageName, (messageData) => { /* callback code */ });`.
|
||||
|
||||
## The message system
|
||||
|
||||
The Graphite editor backend is organized into a hierarchy of subsystems, called *message handlers*, which talk to one another through message passing. Messages are pushed to the front or back of a queue and each one is processed sequentially by the backend's dispatcher.
|
||||
|
||||
The dispatcher lives at the root of the application hierarchy and it owns its message handlers. Thus, Rust's restrictions on mutable borrowing are satisfied because only the dispatcher mutably borrows its message handlers, one at a time, while each message is processed.
|
||||
|
||||
### Messages
|
||||
|
||||
Messages are enum variants that are dispatched to perform some intended activity within their respective message handlers. Here are two `DocumentMessage` definitions:
|
||||
```rs
|
||||
pub enum DocumentMessage {
|
||||
...
|
||||
// A message that carries one named data field
|
||||
DeleteLayer {
|
||||
id: NodeId,
|
||||
}
|
||||
// A message that carries no data
|
||||
DeleteSelectedLayers,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
As shown above, additional data fields can be included with each message. But as a special case denoted by the `#[child]` attribute, that data can also be a sub-message enum, which enables hierarchical nesting of message handler subsystems.
|
||||
|
||||
<br />
|
||||
<details>
|
||||
<summary>To view the hierarchical subsystem file structure: click here</summary>
|
||||
<br />
|
||||
<!--
|
||||
Generated with:
|
||||
cd editor/src/messages
|
||||
tree -P '*_message.rs|*_message_handler.rs|*_tool.rs' --prune
|
||||
Then the first line's "." was replaced with "messages"
|
||||
-->
|
||||
|
||||
```
|
||||
messages
|
||||
├── broadcast
|
||||
│ ├── broadcast_message.rs
|
||||
│ └── broadcast_message_handler.rs
|
||||
├── debug
|
||||
│ ├── debug_message.rs
|
||||
│ └── debug_message_handler.rs
|
||||
├── dialog
|
||||
│ ├── dialog_message.rs
|
||||
│ ├── dialog_message_handler.rs
|
||||
│ ├── export_dialog
|
||||
│ │ ├── export_dialog_message.rs
|
||||
│ │ └── export_dialog_message_handler.rs
|
||||
│ ├── new_document_dialog
|
||||
│ │ ├── new_document_dialog_message.rs
|
||||
│ │ └── new_document_dialog_message_handler.rs
|
||||
│ └── preferences_dialog
|
||||
│ ├── preferences_dialog_message.rs
|
||||
│ └── preferences_dialog_message_handler.rs
|
||||
├── frontend
|
||||
│ └── frontend_message.rs
|
||||
├── globals
|
||||
│ ├── globals_message.rs
|
||||
│ └── globals_message_handler.rs
|
||||
├── input_mapper
|
||||
│ ├── input_mapper_message.rs
|
||||
│ ├── input_mapper_message_handler.rs
|
||||
│ └── key_mapping
|
||||
│ ├── key_mapping_message.rs
|
||||
│ └── key_mapping_message_handler.rs
|
||||
├── input_preprocessor
|
||||
│ ├── input_preprocessor_message.rs
|
||||
│ └── input_preprocessor_message_handler.rs
|
||||
├── layout
|
||||
│ ├── layout_message.rs
|
||||
│ └── layout_message_handler.rs
|
||||
├── portfolio
|
||||
│ ├── document
|
||||
│ │ ├── document_message.rs
|
||||
│ │ ├── document_message_handler.rs
|
||||
│ │ ├── graph_operation
|
||||
│ │ │ ├── graph_operation_message.rs
|
||||
│ │ │ └── graph_operation_message_handler.rs
|
||||
│ │ ├── navigation
|
||||
│ │ │ ├── navigation_message.rs
|
||||
│ │ │ └── navigation_message_handler.rs
|
||||
│ │ ├── node_graph
|
||||
│ │ │ ├── node_graph_message.rs
|
||||
│ │ │ └── node_graph_message_handler.rs
|
||||
│ │ ├── overlays
|
||||
│ │ │ ├── overlays_message.rs
|
||||
│ │ │ └── overlays_message_handler.rs
|
||||
│ │ └── properties_panel
|
||||
│ │ ├── properties_panel_message.rs
|
||||
│ │ └── properties_panel_message_handler.rs
|
||||
│ ├── menu_bar
|
||||
│ │ ├── menu_bar_message.rs
|
||||
│ │ └── menu_bar_message_handler.rs
|
||||
│ ├── portfolio_message.rs
|
||||
│ └── portfolio_message_handler.rs
|
||||
├── preferences
|
||||
│ ├── preferences_message.rs
|
||||
│ └── preferences_message_handler.rs
|
||||
├── tool
|
||||
│ ├── tool_message.rs
|
||||
│ ├── tool_message_handler.rs
|
||||
│ ├── tool_messages
|
||||
│ │ ├── artboard_tool.rs
|
||||
│ │ ├── brush_tool.rs
|
||||
│ │ ├── ellipse_tool.rs
|
||||
│ │ ├── eyedropper_tool.rs
|
||||
│ │ ├── fill_tool.rs
|
||||
│ │ ├── freehand_tool.rs
|
||||
│ │ ├── gradient_tool.rs
|
||||
│ │ ├── imaginate_tool.rs
|
||||
│ │ ├── line_tool.rs
|
||||
│ │ ├── navigate_tool.rs
|
||||
│ │ ├── path_tool.rs
|
||||
│ │ ├── pen_tool.rs
|
||||
│ │ ├── polygon_tool.rs
|
||||
│ │ ├── rectangle_tool.rs
|
||||
│ │ ├── select_tool.rs
|
||||
│ │ ├── spline_tool.rs
|
||||
│ │ └── text_tool.rs
|
||||
│ └── transform_layer
|
||||
│ ├── transform_layer_message.rs
|
||||
│ └── transform_layer_message_handler.rs
|
||||
└── workspace
|
||||
├── workspace_message.rs
|
||||
└── workspace_message_handler.rs
|
||||
```
|
||||
|
||||
<br />
|
||||
</details>
|
||||
|
||||
By convention, regular data must be written as struct-style named fields (shown above), while a sub-message enum must be written as a tuple/newtype-style field (shown below). The `DocumentMessage` enum of the previous example is defined as a child of `PortfolioMessage` which wraps it like this:
|
||||
|
||||
```rs
|
||||
pub enum PortfolioMessage {
|
||||
...
|
||||
// A message that carries the `DocumentMessage` child enum as data
|
||||
#[child]
|
||||
Document(DocumentMessage),
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Likewise, the `PortfolioMessage` enum is wrapped by the top-level `Message` enum. The dispatcher operates on the queue of these base-level `Message` types.
|
||||
|
||||
So for example, the `DeleteSelectedLayers` message mentioned previously will look like this as a `Message` data type:
|
||||
|
||||
```rs
|
||||
Message::Portfolio(
|
||||
PortfolioMessage::Document(
|
||||
DocumentMessage::DeleteSelectedLayers
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Writing out these nested message enum variants would be cumbersome, so that `#[child]` attribute shown earlier invokes a proc macro that automatically implements the `From` trait, letting you write this instead to get a `Message` data type:
|
||||
|
||||
```rs
|
||||
DocumentMessage::DeleteSelectedLayers.into()
|
||||
```
|
||||
|
||||
Most often, this is simplified even further because the `.into()` is called for you when pushing a message to the queue with `.add()` or `.add_front()`. So this becomes as simple as:
|
||||
|
||||
```rs
|
||||
responses.add(DocumentMessage::DeleteSelectedLayers);
|
||||
```
|
||||
|
||||
The `responses` message queue is composed of `Message` data types, and thanks to this system, child messages like `DocumentMessage::DeleteSelectedLayers` are automatically wrapped in their ancestor enum variants to become a `Message`, saving you from writing the verbose nested form.
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
+++
|
||||
title = "Code structure"
|
||||
|
||||
[extra]
|
||||
order = 1 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
## Tech stack
|
||||
|
||||
- rustc: Compiler for node graph generics and custom nodes
|
||||
- rust-gpu: Compiler backend to generate compute shaders from Rust source code
|
||||
- wgpu: Portable graphics API for running compute shaders on desktop and web
|
||||
- Tauri: lightweight desktop web UI shell while the backend runs natively (experimental)
|
||||
<!-- - Vello: GPU-accelerated vector graphics renderer -->
|
||||
<!-- - COSMIC Text: Text shaping and typesetting -->
|
||||
<!-- - Wasmer or Wasmtime: Portable, sandboxed runtime for custom nodes -->
|
||||
<!-- - Tokio: parallelized job execution in the node graph pipeline -->
|
||||
<!-- - Xilem: High-performance native UI framework, to replace Tauri when ready -->
|
||||
|
||||
## Frontend/backend communication
|
||||
|
||||
The Graphite editor frontend is the web code which displays the user interface. It passes user interactions to the backend. The Graphite editor backend handles all the day-to-day logic and responsibilities of a user-facing interactive application. Some duties include: user input, GUI state management, viewport tool behavior, layer management and selection, and handling of multiple document tabs.
|
||||
|
||||
Frontend (TS) -> backend (Rust/wasm) communication is achieved through a thin Rust translation layer in `/frontend/wasm/src/editor_api.rs` which wraps the Editor backend's complex Rust data type API and provides the TS with a simpler API of callable functions. These wrapper functions are compiled by wasm-bindgen into autogenerated TS functions that serve as an entry point into the wasm.
|
||||
|
||||
Backend (Rust) -> frontend (TS) communication happens by sending a queue of messages to the frontend message dispatcher. After the TS has called any wrapper API function to get into backend (Rust) code execution, the Editor's business logic runs and queues up `FrontendMessage`s (defined in `/editor/src/messages/frontend/frontend_message.rs`) which get mapped from Rust to TS-friendly data types in `/frontend/src/wasm-communication/messages.ts`. Various TS code subscribes to these messages by calling `subscribeJsMessage(MessageName, (messageData) => { /* callback code */ });`.
|
||||
|
||||
## The message system
|
||||
|
||||
The Graphite editor backend is organized into a hierarchy of systems, called *message handlers*, which talk to one another through message passing. Messages are pushed to the front or back of a queue and each one is processed sequentially by the backend's dispatcher. The dispatcher lives at the root of the application hierarchy and it owns its message handlers. Thus, Rust's restrictions on mutable borrowing are satisfied because only the dispatcher mutably borrows its message handlers, one at a time, while each message is processed.
|
||||
|
||||
### Messages
|
||||
|
||||
Messages are enum variants that are dispatched to perform some intended activity within their respective message handlers. Here are two `DocumentMessage` definitions:
|
||||
```rs
|
||||
pub enum DocumentMessage {
|
||||
...
|
||||
// A message that carries one named data field
|
||||
DeleteLayer {
|
||||
id: NodeId,
|
||||
}
|
||||
// A message that carries no data
|
||||
DeleteSelectedLayers,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
As shown above, additional data fields can be included with each message. But as a special case denoted by the `#[child]` attribute, that data can also be a sub-message, which enables us to nest message handler systems hierarchically. By convention, regular data must be written as struct-style named fields (shown above), while a sub-message must be written as an unnamed tuple/newtype-style field (shown below). The `DocumentMessage` enum of the previous example is defined as a child of `PortfolioMessage` which wraps it like this:
|
||||
|
||||
```rs
|
||||
pub enum PortfolioMessage {
|
||||
...
|
||||
// A message that carries the `DocumentMessage` child enum as data
|
||||
#[child]
|
||||
Document(DocumentMessage),
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Likewise, the `PortfolioMessage` enum is wrapped by the top-level `Message` enum. The dispatcher operates on the queue of these base-level `Message` types.
|
||||
|
||||
So for example, the `DeleteSelectedLayers` message mentioned previously will look like this as a `Message` data type:
|
||||
|
||||
```rs
|
||||
Message::Portfolio(
|
||||
PortfolioMessage::Document(
|
||||
DocumentMessage::DeleteSelectedLayers
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Writing out these nested message enum variants would be cumbersome, so that `#[child]` attribute shown earlier invokes a proc macro that automatically implements the `From` trait, letting you write this instead to get a `Message` data type:
|
||||
|
||||
```rs
|
||||
DocumentMessage::DeleteSelectedLayers.into()
|
||||
```
|
||||
|
||||
Most often, this is simplified even further because the `.into()` is called for you when pushing a message to the queue with `.add()` or `.add_front()`. So this becomes as simple as:
|
||||
|
||||
```rs
|
||||
responses.add(DocumentMessage::DeleteSelectedLayers);
|
||||
```
|
||||
|
||||
The `responses` message queue is composed of `Message` data types, and thanks to this system, child messages like `DocumentMessage::DeleteSelectedLayers` are automatically wrapped in their ancestor enum variants to become a `Message`, saving you from writing the verbose nested form.
|
|
@ -1,47 +0,0 @@
|
|||
+++
|
||||
title = "Contributing guidelines"
|
||||
|
||||
[extra]
|
||||
order = 3 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
## Code style
|
||||
|
||||
The Graphite project prizes code quality and accessibility to new contributors. Therefore, we ask you please make all efforts to contribute readable, well-documented code according to these best practices.
|
||||
|
||||
### Naming
|
||||
|
||||
Please use descriptive variable/function/symbol names and keep abbreviations to a minimum. Prefer to spell out full words most of the time, so `gen_doc_fmt` should be written out as `generate_document_format` instead.
|
||||
|
||||
This avoids the mental burden of expanding abbreviations into semantic meaning. Monitors are wide enough to display long variable/function names, so descriptive is better than cryptic. To streamline code review, it's recommended that you set up a spellcheck plugin in your editor. The project uses American English spelling conventions.
|
||||
|
||||
### Linting
|
||||
|
||||
Please ensure Clippy is enabled. This should be set up automatically in VS Code. Try to avoid committing code with lint warnings.
|
||||
|
||||
### Comments
|
||||
|
||||
For consistency, please try to write comments in *Sentence case* (starting with a capital letter). End with a period only if multiple sentences are used in the same comment. For doc comments (`///`), always write in full sentences (ending with a period).
|
||||
|
||||
Comments should be placed on a separate line, but exceptions are permitted where sensible. They should target the maximum line length of 200 characters (don't go over, and don't target a considerably lower number like 80 for line breaks).
|
||||
|
||||
### Imports
|
||||
|
||||
At the top of Rust files, please follow the convention of separating imports into three blocks, in this order:
|
||||
1. Local (`use super::` and `use crate::`)
|
||||
2. First-party crates (e.g. `use editor::`)
|
||||
3. Third-party libraries (e.g. `use std::` or `use glam::`)
|
||||
|
||||
Combine related imports with common paths at the same depth. For example, the lines `use crate::A::B::C;`, `use crate::A::B::C::Foo;`, and `use crate::A::B::C::Bar;` should be combined into `use crate::A::B::C::{self, Foo, Bar};`. But do not combine imports at mixed path depths. For example, `use crate::A::{B::C::Foo, X::Hello};` should be split into two separate import lines. In simpler terms, avoid putting a `::` inside `{}`.
|
||||
|
||||
## Tests
|
||||
|
||||
It's great if you can write tests for your code, especially if it's a tricky stand-alone function. However at the moment, we are prioritizing rapid iteration and will usually accept code without associated unit tests. That stance will change in the near future as we begin focusing more on stability than iteration speed.
|
||||
|
||||
## Draft pull requests
|
||||
|
||||
Once you begin writing code, please open a pull request immediately and mark it as a **Draft**. Please push to this on a frequent basis, even if things don't compile or work fully yet. It's very helpful to have your work-in-progress code up on GitHub so the status of your feature is less of a mystery.
|
||||
|
||||
Open a new PR as a draft / convert an existing PR to a draft:
|
||||
|
||||
<img src="https://static.graphite.rs/content/volunteer/guide/draft-pr.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Screenhots showing GitHub's "Create pull request (arrow) > Create draft pull request" and "Still in progress? Convert to draft" buttons" />
|
|
@ -0,0 +1,36 @@
|
|||
+++
|
||||
title = "Debugging tips"
|
||||
|
||||
[extra]
|
||||
order = 4 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
The Wasm-based editor has some unique limitations about how you are able to debug it. This page offers tips and best practices to get the most out of your problem-solving efforts.
|
||||
|
||||
## Comparing with deployed builds
|
||||
|
||||
When tracking down a bug, first check if the issue you are noticing also exists in `master` or just your branch. Open up [dev.graphite.rs](https://dev.graphite.rs) which always deploys the lastest commit, compared to [editor.graphite.rs](https://editor.graphite.rs) which is manually deployed from time to time for the sake of stability.
|
||||
|
||||
Use *Help* > *About Graphite* in the editor to view any build's Git commit hash.
|
||||
|
||||
Beware of one potential pitfall: all deploys and build links are built with release optimizations enabled. This means some bugs (like crashes from bounds checks or debug assertions) may exist in `master` and would appear if run locally, but not in the deployed version.
|
||||
|
||||
## Printing to the console
|
||||
|
||||
Use the browser console (<kbd>F12</kbd>) to check for warnings and errors. Use the Rust macro `debug!("The number is {}", some_number);` to print to the browser console. These statements should be for temporary debugging. Remove them before your code is reviewed. Print-based debugging is necessary because breakpoints are not supported in WebAssembly.
|
||||
|
||||
Additional print statements are available that *should* be committed.
|
||||
|
||||
- `error!()` is for descriptive user-facing error messages arising from a bug
|
||||
- `warn!()` is for non-critical problems that likely indicate a bug somewhere
|
||||
- `trace!()` is for verbose logs of ordinary internal activity, hidden by default
|
||||
|
||||
To show `trace!()` logs, activate *Help* > *Debug: Print Trace Logs*.
|
||||
|
||||
## Message system logs
|
||||
|
||||
To also view logs of the messages dispatched by the message system, activate *Help* > *Debug: Print Messages* > *Only Names*. Or use *Full Contents* for a more verbose view containing the actual data being passed. This is an invaluable window into the activity of the message flow and works well together with `debug!()` printouts for tracking down message-related defects.
|
||||
|
||||
## Node/layer and document IDs
|
||||
|
||||
In debug mode, hover over a layer's name in the Layers panel, or a layer/node in the node graph, to view a tooltip with its ID. Likewise, document IDs may be read from their tab tooltips.
|
|
@ -1,30 +0,0 @@
|
|||
+++
|
||||
title = "Debugging"
|
||||
|
||||
[extra]
|
||||
order = 2 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
## Deployed builds
|
||||
|
||||
When tracking down a bug, first check if the issue you are noticing also exists in `master` or just your branch. Use [dev.graphite.rs](https://dev.graphite.rs) which should always deploy the lastest commit on `master`. By comparison, [editor.graphite.rs](https://editor.graphite.rs) is manually updated every few days or weeks to ensure stability. Use *Help* > *About Graphite* in the editor to view the build's [commit hash](https://github.com/GraphiteEditor/Graphite/commits/master).
|
||||
|
||||
## Printing to the console
|
||||
|
||||
Use the browser console (<kbd>F12</kbd>) to check for warnings and errors. Use the Rust macro `debug!("A debug message");` to print to the browser console. These statements should be for temporary debugging. Remove them before committing to `master`. Print-based debugging is necessary because breakpoints are not supported in WebAssembly.
|
||||
|
||||
Additional print statements are available that *should* be committed.
|
||||
|
||||
- `error!()` is for descriptive user-facing error messages arising from a bug
|
||||
- `warn!()` is for non-critical problems that likely indicate a bug somewhere
|
||||
- `trace!()` is for verbose logs of ordinary internal activity, hidden by default
|
||||
|
||||
To show `trace!()` logs, activate *Help* > *Debug: Print Trace Logs*.
|
||||
|
||||
## Message system logs
|
||||
|
||||
To also view logs of the messages dispatched by the message system, activate *Help* > *Debug: Print Messages* > *Only Names*. Or use *Full Contents* for more verbose insight with the actual data being passed. This is an invaluable window into the activity of the message flow and works well together with `debug!()` printouts for tracking down message-related issues.
|
||||
|
||||
## Node/layer and document IDs
|
||||
|
||||
In debug mode, hover over a layer's name in the Layers panel, or a layer/node in the node graph, to view a tooltip with its ID. Likewise, document IDs may be read by hovering over their tabs.
|
|
@ -1,14 +0,0 @@
|
|||
+++
|
||||
title = "Editor and tooling"
|
||||
|
||||
[extra]
|
||||
order = 1 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
We provide default configurations for VS Code users. When you open the project, watch for a prompt to install the project's suggested extensions. They will provide helpful web and Rust tooling. If you use a different IDE, you won't get default configurations for the project out of the box, so please remember to format your code and check CI for errors.
|
||||
|
||||
## Checking, linting, and formatting
|
||||
|
||||
While developing Rust code, `cargo check`, `cargo clippy`, and `cargo fmt` terminal commands may be run from the root directory. For web code, `npm run lint` and `npm run lint-no-fix` can be used from the `/frontend` directory to fix or view formatting issues.
|
||||
|
||||
If you don't use VS Code and its format-on-save feature, please remember to format before committing or consider [setting up a `pre-commit` hook](https://githooks.com/) to do that automatically. Disabling VS Code's *Auto Save* files feature is recommended to ensure you actually save (and thus format) file changes.
|
|
@ -1,14 +0,0 @@
|
|||
+++
|
||||
title = "Getting help"
|
||||
|
||||
[extra]
|
||||
order = 3 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
## Discord developer community
|
||||
|
||||
Join the [project's Discord server](https://discord.graphite.rs) then hop on the `#development` channel and ping @Keavon, @TrueDoctor, or @0Hypercube. The team would be delighted to help you get started by providing in-depth explanations of the code and programming assistance as you work. Please do not hesitate to reach out right away!
|
||||
|
||||
## Code documentation
|
||||
|
||||
Look out for `README.md` files in some folders of the codebase and doc comments at the top of some Rust files. The quantity of those files is limited right now, but documenting code is an excellent contribution if you wish to explain what you've learned for the sake of others, and improve your own understanding in the process.
|
|
@ -1,12 +0,0 @@
|
|||
+++
|
||||
title = "Picking a task"
|
||||
|
||||
[extra]
|
||||
order = 2 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
The [task board](https://github.com/orgs/GraphiteEditor/projects/1/views/1) provides a list of [available tasks](https://github.com/orgs/GraphiteEditor/projects/1/views/5), as well as a [beginner-friendly](https://github.com/orgs/GraphiteEditor/projects/1/views/6) subset. Issues partially or fully involving web development can also be seen [listed here](https://github.com/orgs/GraphiteEditor/projects/1/views/5?filterQuery=status%3AShort-Term%2CMedium-Term%2CLonger-Term+label%3AWeb+-label%3ARust) which may involve HTML/CSS/TypeScript/Svelte, although depending on the task, it may also involve Rust (which can be a good way to get gently introduced to the language if you come from a web background).
|
||||
|
||||
Writing new documentation by commenting existing code is another valuable way to contribute as you learn from reading code.
|
||||
|
||||
Feel free to pick whatever task interests you, then comment on the issue that you would like to start. After commenting, you can dig in right away, then we will assign the issue to you once you have a PR ready. (Always remembering to leave a comment is important, since GitHub doesn't allow assigning issues to people who haven't commented on them.)
|
|
@ -4,11 +4,10 @@ template = "book.html"
|
|||
page_template = "book.html"
|
||||
|
||||
[extra]
|
||||
order = 3 # Chapter number
|
||||
js = ["video-embed.js"]
|
||||
order = 5 # Chapter number
|
||||
+++
|
||||
|
||||
**Graphene** is the node graph engine that powers the Graphite editor.
|
||||
Graphene is the node graph engine that powers the Graphite editor.
|
||||
|
||||
It's hard to describe in one sentence precisely what Graphene is, because it's a technology that serves several roles when viewed from different angles. But to get a feel for what it encompasses, here is a list of some of its purposes:
|
||||
|
||||
|
@ -89,7 +88,7 @@ Since Graphene is fundamentally a programming language, throughout this document
|
|||
| Graph execution | Program execution |
|
||||
|
||||
<!-- Our philosophy of building (bootstrapping) our own higher-level language features from the language itself -->
|
||||
<!-- Primary inputs/outputs, secondary inputs/outputs, `.eval()`, recompiling when secondary input values are updated but not when primary input data is updated -->
|
||||
<!-- Call arguments, construction arguments, `.eval()`, recompiling when construction argument values are updated but not when call argument data changes -->
|
||||
<!-- Compose nodes and automatic/manual composition -->
|
||||
<!-- Extract/inject nodes and metaprogramming -->
|
||||
<!-- Cache nodes and stable node IDs -->
|
||||
|
|
|
@ -13,7 +13,7 @@ Any (sub)graph can import/export data from/to the outside world. For example, a
|
|||
|
||||
In the Graphite editor UI, here is an example graph of artwork that imports no data but exports its content to the canvas:
|
||||
|
||||
<img src="https://static.graphite.rs/content/index/gui-mockup-nodes__5.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Node graph UI mockup" data-carousel-image />
|
||||
<img src="https://static.graphite.rs/content/index/gui-mockup-nodes__5.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Node graph UI mockup" />
|
||||
|
||||
The graph shown above represents the full artwork, meaning it's the root-level graph in its document. But there is nothing special about that graph compared to any subgraph. To avoid the confusion of calling it a graph or subgraph which comes with implications about user-facing concepts in the context of a document, we will use the less-ambiguous term **network** in the context of Graphene's internal concepts and codebase.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
+++
|
||||
title = "Getting started"
|
||||
title = "Project setup"
|
||||
template = "book.html"
|
||||
page_template = "book.html"
|
||||
|
||||
|
@ -29,6 +29,7 @@ Regarding the last one: you'll likely get faster build times if you manually ins
|
|||
<br />
|
||||
<details>
|
||||
<summary>Linux users: click here</summary>
|
||||
<br />
|
||||
|
||||
On Linux, you likely need to install this set of additional packages which are required by Tauri, even if you're just building the web app:
|
||||
|
||||
|
@ -65,11 +66,14 @@ From either the `/` (root) or `/frontend` directories, you can run the project b
|
|||
npm start
|
||||
```
|
||||
|
||||
This spins up the dev server at <http://localhost:8080> with a file watcher that performs hot reloading of the web page. You should be able to start the server, edit and save web and Rust code, and shut it down by double pressing <kbd>Ctrl</kbd><kbd>C</kbd>. You sometimes may need to reload the browser's page if hot reloading didn't behave right.
|
||||
This spins up the dev server at <http://localhost:8080> with a file watcher that performs hot reloading of the web page. You should be able to start the server, edit and save web and Rust code, and shut it down by double pressing <kbd>Ctrl</kbd><kbd>C</kbd>. You sometimes may need to reload the browser's page if hot reloading didn't behave right— always refresh when Rust recompiles.
|
||||
|
||||
This method compiles Graphite code in debug mode which includes debug symbols for viewing function names in stack traces. But be aware, it runs slower and the Wasm binary is much larger. Having your browser's developer tools open will also significantly impact performance in both debug and release builds, so it's best to close that when not in use.
|
||||
|
||||
## Production builds
|
||||
<br />
|
||||
<details>
|
||||
<summary>Production build instructions: click here</summary>
|
||||
<br />
|
||||
|
||||
You'll rarely need to compile your own production builds because our CI/CD system takes care of deployments. However, you can compile a production build with full optimizations by first installing the additional `cargo-about` dev dependency:
|
||||
|
||||
|
@ -84,3 +88,15 @@ npm run build
|
|||
```
|
||||
|
||||
This produces the `/frontend/dist` directory containing the static site files that must be served by your own web server.
|
||||
|
||||
</details>
|
||||
|
||||
## Development tooling
|
||||
|
||||
We provide default configurations for VS Code users. When you open the project, watch for a prompt to install the project's [suggested extensions](https://github.com/GraphiteEditor/Graphite/blob/master/.vscode/extensions.json). They will provide helpful web and Rust tooling. If you use a different IDE, you won't get default configurations for the project out of the box, so please remember to format your code and check CI for errors.
|
||||
|
||||
### Checking, linting, and formatting
|
||||
|
||||
While developing Rust code, `cargo check`, `cargo clippy`, and `cargo fmt` terminal commands may be run from the root directory. For web code, formatting issues can be linted using `npm run lint` (to view) and `npm run lint-fix` (to fix) if run from the `/frontend` directory.
|
||||
|
||||
If you don't use VS Code and its format-on-save feature, please remember to format before committing or [set up a `pre-commit` hook](https://githooks.com/) to do that automatically. Disabling VS Code's *Auto Save* files feature is recommended to ensure you actually save (and thus format) file changes.
|
|
@ -1,30 +0,0 @@
|
|||
+++
|
||||
title = "Projects"
|
||||
template = "book.html"
|
||||
page_template = "book.html"
|
||||
|
||||
[extra]
|
||||
order = 4 # Chapter number
|
||||
+++
|
||||
|
||||
Graphite is built from a number of separate projects, each with a distinct focus. New developers may choose to specialize in one or more area without having to attain a working knowledge of the entire codebase. This chapter details each project's purpose, what knowledge or background is best suited for getting involved with it, and how to begin making useful contributions. Opportunities also exist for contributing to specific self-contained sub-projects for students interested in completing a Google Summer of Code or other internship program, a university capstone project, or another similar endeavor.
|
||||
|
||||
## Frontend
|
||||
|
||||
The frontend is the interface for Graphite which users see and interact with. It is built using web technologies with TypeScript, Svelte, and SCSS. The frontend's philosophy is to be as lightweight and minimal as possible. It acts as the entry point for user input and quickly hands off its work to the WebAssembly editor backend via its Wasm wrapper API. That API is written in Rust but has TypeScript bindings generated by the wasm-bindgen tooling that is part of the Vite-based build chain. The frontend is built of many components that recursively form the window, panels, and widgets that make up the user interface.
|
||||
|
||||
## Editor
|
||||
|
||||
The editor is the core of the Graphite application, and it's where all the business logic occurs for the tooling and user interaction. It is written in Rust and compiled to WebAssembly, and at its heart is the message system [described here](../codebase-overview/code-structure#the-message-system).
|
||||
|
||||
## Graphene
|
||||
|
||||
Graphene is the node graph engine which manages and renders the documents. It is itself a programming language, where Graphene programs are compiled while being edited live by the user, and where executing the program renders the document.
|
||||
|
||||
## Libraries
|
||||
|
||||
Graphite maintains several Rust libraries that are published to crates.io for use by other developers, in addition to internal use by Graphite itself.
|
||||
|
||||
### Bezier-rs
|
||||
|
||||
Bezier-rs is a computational geometry library. It provides a rich selection of types and functions for working with Bézier segments and paths. There is also an [interactive demo](/libraries/bezier-rs/) gallery. This was initially developed as a successful [student project](./student-projects). Contribution opportunities include optimization, code cleanup and refactoring, and implementing new algorithms.
|
16
website/content/volunteer/guide/starting-a-task/_index.md
Normal file
16
website/content/volunteer/guide/starting-a-task/_index.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
+++
|
||||
title = "Starting a task"
|
||||
template = "book.html"
|
||||
page_template = "book.html"
|
||||
|
||||
[extra]
|
||||
order = 3 # Chapter number
|
||||
+++
|
||||
|
||||
There are two places to look for beginner-friendly development tasks. Usually, the best option is to select one of the many bite-sized task descriptions marked with a <span class="emoji">‼️</span> reaction in the `#✅code-todo-list` channel of the [Discord server](https://discord.graphite.rs). You may also browse the [task board](https://github.com/orgs/GraphiteEditor/projects/1/views/1), which lists [beginner issues](https://github.com/orgs/GraphiteEditor/projects/1/views/6) to pick from. The Discord option usually has the more approachable tasks compared to the GitHub issues, which tend to have more variability in complexity.
|
||||
|
||||
If you're unsure about which task to pick, feel free to ask in the `#📄development` channel.
|
||||
|
||||
You may right click a `#✅code-todo-list` task and select "Create Thread" to ask questions and discuss your development progress. If your work doesn't correspond to a specific listed task in that channel, you can also create a thread in `#🧵task-help` with a short, descriptive title ending with your issue or PR number.
|
||||
|
||||
If you're tackling a GitHub issue, please remember to comment in the issue with a link to your PR once you submit it. This is necessary because we will assign you to the issue after your PR has merged, but GitHub only allows assignments to those who have commented on the issue.
|
|
@ -0,0 +1,61 @@
|
|||
+++
|
||||
title = "Code quality guidelines"
|
||||
|
||||
[extra]
|
||||
order = 2 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
The Graphite project prizes code quality and accessibility to new contributors. Therefore, we ask you please make all efforts to contribute readable, well-documented code according to these best practices.
|
||||
|
||||
## Linting
|
||||
|
||||
Please ensure Clippy is enabled. This should be set up automatically in VS Code. Try to avoid committing code with lint warnings. You may execute `cargo clippy` anytime to confirm.
|
||||
|
||||
## Naming
|
||||
|
||||
Please use descriptive variable/function/symbol names and keep abbreviations to a minimum. Prefer spelling out full words most of the time, so `gen_doc_fmt` should be written out as `generate_document_format` instead.
|
||||
|
||||
This avoids the mental burden of expanding abbreviations into semantic meaning. Monitors are wide enough to display long variable/function names, so descriptive is better than cryptic.
|
||||
|
||||
Totally unambiguous, common shortened forms are acceptable such as "max" for "maximum", "eval" for "evaluate", and "info" for "information".
|
||||
|
||||
To avoid wasted effort in code review, it's recommended that you set up a spellcheck plugin, like [this extension](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) for VS Code. The project uses American English spelling conventions.
|
||||
|
||||
## Whole-number floats
|
||||
|
||||
Always use the style `42.` instead of `42.0` for whole-number floats to maintain consistency and brevity. For range syntax, either `0.0..42.` or `(0.)..42.` is acceptable.
|
||||
|
||||
## Comments
|
||||
|
||||
For consistency, please try to write comments in *Sentence case* (starting with a capital letter). End with a period only if multiple sentences are used in the same comment. For doc comments (`///`), always write in full sentences ending with a period. There should always be one space after the `//` or `///` comment markers, and `/* */` style comments shouldn't be used.
|
||||
|
||||
Avoid including commented-out code, unless you have a compelling reason to keep it around for future adaption, in your PRs that are open for code review.
|
||||
|
||||
Comments should usually be placed on a separate line above the code they are referring to, not at the end of the code line.
|
||||
|
||||
## Blank lines
|
||||
|
||||
Please make a habit of grouping together related lines of codes in blocks separated by blank lines. If you have dozens of lines comprising a single unbroken block of logic, you are likely not splitting it apart enough to aid readability. Find sensible places to partition the logic and insert blank lines between each. Roughly 10% of the code you write should ideally be blank lines, otherwise you are likely underutilizing them at the expense of readability.
|
||||
|
||||
## Imports
|
||||
|
||||
Our imports used to be a mess before we tamed the chaos with these rules.
|
||||
|
||||
At the top of Rust files, use the convention of separating imports into three blocks, ordered as:
|
||||
1. Local (`use super::` and `use crate::`)
|
||||
2. First-party crates (e.g. `use editor::` or `bezier_rs::`)
|
||||
3. Third-party libraries (e.g. `use std::` or `use glam::`)
|
||||
|
||||
Combine related imports with common paths at the same depth. For example:
|
||||
|
||||
```rs
|
||||
use crate::A::B::C;
|
||||
use crate::A::B::C::Foo;
|
||||
use crate::A::B::C::Bar;
|
||||
|
||||
// Should be combined into:
|
||||
|
||||
use crate::A::B::C::{self, Foo, Bar};
|
||||
```
|
||||
|
||||
But do not combine imports at mixed path depths. For example, `use crate::A::{B::C::Foo, X::Hello};` should be split into two separate import lines— avoid having `::` inside `{}`.
|
|
@ -0,0 +1,107 @@
|
|||
+++
|
||||
title = "Submitting a contribution"
|
||||
|
||||
[extra]
|
||||
order = 3 # Page number after chapter intro
|
||||
css_external = ["https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap"]
|
||||
+++
|
||||
|
||||
Collaboration is a key part of real-world software engineering. Graphite follows some basic procedures to keep the process smooth and efficient. You will want to familiarize yourself with these guidelines to save yourself and Graphite maintainers time and confusion.
|
||||
|
||||
This assumes you understand enough about how Git works to utilize commits, branches, and multiple remotes. If you're new to Git, you will need to learn those topics on your own, but a good starting point is [this portion](https://youtu.be/vUzIeg8frh4?t=237) of the Graphite intro webcast which recommends installing the [Git Graph](https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph) extension for VS Code to visualize your Git history and branches.
|
||||
|
||||
## Git branch name
|
||||
|
||||
Before making your first commit, create a new branch with a name that describes what it's about. Aim for short but sufficiently descriptive. Kebab-case (using hyphens between words) is our usual convention. Don't include a prefix like `feature/` or `fix/` which just adds visual noise. An example like `fix-path-tool-selection-history` is fine, but almost too long.
|
||||
|
||||
Rename it if you already made a branch with a different name. Create a new branch if you've been committing to `master` or another existing branch. If your branch is specifically called `master`, it becomes harder to work with during code reviews.
|
||||
|
||||
After you push your branch to GitHub then open a PR, you won't be able to change its name. But please don't close a PR and open a new one just because the branch name isn't optimal. Just keep these tips in mind for the next time.
|
||||
|
||||
## Pull request
|
||||
|
||||
Once you have gotten your code far enough along that you are confident you'll be able to complete it, open a pull request (PR). You might also do this earlier if a maintainer requests to see your code in order to assist you.
|
||||
|
||||
Later on when you are building larger features, a PR should be opened once you have meaningful progress. That way, it can be kept safe on GitHub and other maintainers can check in to see your status so your work is less of a mystery.
|
||||
|
||||
Here's the important part: when you open a PR, it should be marked as a draft unless it is currently ready for review. The left image shows how to open a new PR as a draft, and the right image shows how to convert an existing PR to a draft.
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/volunteer/guide/draft-pr.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Screenhots showing GitHub's "Create pull request (arrow) > Create draft pull request" and "Still in progress? Convert to draft" buttons" /></p>
|
||||
|
||||
<center><em>Open a new PR as a draft / convert an existing PR to a draft</em></center>
|
||||
|
||||
You should mark it as ready for review and ping a maintainer when you believe your code implements the needed functionality and doesn't introduce any new bugs or broken features.
|
||||
|
||||
## Title and description
|
||||
|
||||
Your PR title will become the commit message of your feature's commit in the project Git history. It should aim to concisely but descriptively summarize what your PR does. We use sentence case and imperative mood ("Fix X bug", "Add Y feature", "Make Z faster").
|
||||
|
||||
If you are working on a task from the `#✅code-todo-list` Discord channel, you should right-click the exact message, select "Copy Message Link", and paste that into your PR description.
|
||||
|
||||
If you are working on a task with a GitHub issue, please be certain to include that issue number in the description. GitHub requires the format "Closes #123", "Fixes #123", or "Resolves #123". If there are multiple issues, you have to fully repeat this trigger word for each one. If there is no issue, remove the pre-filled "Closes #" description text.
|
||||
|
||||
When the PR gets merged, any issue referenced by the trigger word will be automatically closed. That isn't desirable for [tracking issues](https://github.com/GraphiteEditor/Graphite/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+%22tracking+issue%22), so you should instead use "Part of #123" which isn't a trigger word. After the PR merges, please edit the description to change "Part of" to "Closes" so the tracking issue links to the PR without it having gotten closed.
|
||||
|
||||
As a bonus, it can be helpful for maintainers if you take a few minutes to write about what you changed and include relevant screenshots or video clips.
|
||||
|
||||
If you have concerns about a certain approach you took or if a certain part of your code is as clean as it could be, you can leave comments on lines of your own code from the "Files changed" tab after opening the PR.
|
||||
|
||||
## Comment on the issue
|
||||
|
||||
For any issue referenced in your PR (including tracking issues), we need you to leave a comment on issue. It doesn't matter what you write. You can just say "I opened PR #456" or something to that effect. This is only necessary because we will need to assign that issue to you upon merging your PR, but GitHub only allows assignments to those who have commented.
|
||||
|
||||
That way you get credit for your work and we can keep our closed issues cleanly organized. For consistency, a closed issue should have an assignee if it was resolved by a PR. Otherwise, only duplicate or invalid issues should be closed without an assignee.
|
||||
|
||||
We don't commonly assign issues while a PR is still in progress, only upon landing the PR. That's because PRs often get abandoned and we don't want an assignment blocking someone else from picking up the work.
|
||||
|
||||
## Code review etiquette
|
||||
|
||||
It is your responsibility to build the editor, thoroughly test your work, and employ common sense to avoid wasting a maintainer's time in needing to point out obvious flaws. It is not uncommon for inexperienced contributors to request review when their code entirely fails to implement the task at hand, or breaks surrounding functionality in a way that should have been immediately apparent. This doesn't leave a good impression and can frustrate maintainers.
|
||||
|
||||
If you don't actually understand what is intended with your feature/fix and why this is meaningful to a user of Graphite, spend time becoming that user and understanding the context. [Learning](/learn) at least the basics of using Graphite is important. Then ask questions in Discord if you're still confused about specific edge cases or the wording of the task.
|
||||
|
||||
It is also common for larger tasks to enter a round of review to confirm the direction is correct before you go back and polish the remaining details of the implementation. It's good to be in touch with the team to decide on when is the right time for this kind of preliminary review. It can save you effort reworking problems if you misunderstand the goals, or if the exact details of the requirements were never well-defined and you'll need to iterate on the design together with the team. Don't feel that every part of your PR needs to be 100% finished before requesting feedback, but also be clear so you aren't taking a maintainer away from other work to point out that you are obviously nowhere near done.
|
||||
|
||||
## Self-review
|
||||
|
||||
Before marking your PR as ready for review, you should do a self-review. That means reading over the diff of all your changes to ensure they are correct, complete, and lacking frivolous changes like unintended whitespace alterations, leftover debugging code, or commented-out lines. Read over it with a fine-toothed comb so maintainers don't have to nitpick as much. It is only fair that your first code reviewer should be yourself, so you catch the obvious flaws first.
|
||||
|
||||
## Passing CI
|
||||
|
||||
Upon pushing a commit to your PR's branch, CI will need to build and test your code. PRs from forks will have to wait until a maintainer approves the CI run. If you're uncertain, run `cargo test --all-features` on your machine or ask a maintainer to trigger CI for you.
|
||||
|
||||
You also have to pass `cargo fmt` and `cargo clippy` locally before your PR can be merged.
|
||||
|
||||
Your goal is for the check called "Editor: Dev & CI / build (pull_request)" to pass with a <span class="emoji">✅</span>. If it fails with a <span class="emoji">❌</span>, you will need to investigate. If you need access to the build logs, ask a maintainer to provide them. Occasionally, other checks may fail, but you likely won't be responsible for fixing those and they can be ignored.
|
||||
|
||||
## Keeping your work up-to-date
|
||||
|
||||
Be sure to start your work from the latest commit on the `master` branch by pulling (`git pull`) with `master` checked out when you begin coding.
|
||||
|
||||
As time goes on and `master` accumulates new commits, your branch will become outdated. It has to be synced up with `master` before your PR can be merged. Sometimes there will be conflicts that you need to resolve, which you can find learning resources for online. Enabling Git's three-way diff conflict style with `git config --global merge.conflictstyle diff3` can make this process easier.
|
||||
|
||||
When your branch can be updated with `master` without conflicts, you can click the "Update branch" button below the CI status. If you click the dropdown button beside it, you can choose instead to update with a rebase. If this can be done without conflicts, this is preferred because it maintains a clean, linear history for your branch.
|
||||
|
||||
<p><img src="https://static.graphite.rs/content/volunteer/guide/update-branch-with-rebase.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" onload="this.width = this.naturalWidth / 2" alt="Screenhots showing GitHub's "Update with rebase" button" /></p>
|
||||
|
||||
Be sure to pull the rebased, or updated-with-a-merge-commit, branch after you or a maintainer updates it (or pushes other commits to it) to ensure you are working on the latest code.
|
||||
|
||||
## Review process
|
||||
|
||||
Assuming you have done what's explained above, a maintainer will aim to review your PR within a few days if possible. Feel free to send reminders because PRs can get overlooked.
|
||||
|
||||
As a rule of thumb, at this stage you are about 50% done with your work. The other 50% of your time will be spent responding to feedback and making (sometimes significant) changes.
|
||||
|
||||
There are two parts to the review process, QA and code review, which occur separately:
|
||||
|
||||
- Quality assurance (QA): A build of your code will be opened and tested to ensure it implements the requested functionality and doesn't introduce regressions. This is not a substitute for your own testing, but it is a necessary line of defense against overlooked issues. This is usually performed by Keavon, the founder and product designer, whose eye for detail keeps the app polished and consistent. Maintainers (and only maintainers) have the ability to invoke CI by commenting "!build" on your PR which will produce a build link. That is a unique link hosting a build of your PR's current code.
|
||||
- Code review: The code will be checked for flawed approaches, pitfalls, confusing logic, [style guide](../code-quality-guidelines) adherence, sufficient comments and tests, and general quality. A review may be left through GitHub or your PR may have commits added to it. Feel free to read the diffs of those commits to understand what was changed so you can learn from that feedback. Direct commits are often faster than leaving dozens of comments. These can range from nitpicks to larger improvements. Our process is to collaborate on PRs as a team to write the best code possible, meaning your PR won't always be exclusively written by you.
|
||||
|
||||
When changes are requested, the maintainer will usually mark the PR as a draft again while awaiting your updates. It is your responsibility to mark it as ready for review again once you've addressed the feedback.
|
||||
|
||||
- If a PR is a draft, the ball is in your court to move it forward.
|
||||
- If it's marked as ready for review, it means there is nothing more for you to do until the maintainer has time to review it.
|
||||
|
||||
After any number of back-and-forth cycles, a maintainer (usually Keavon who often gives the final say) will merge your PR. All your commits will be squashed into a single new commit on the `master` branch. This keeps the Git history linear and easy to follow.
|
||||
|
||||
Congratulations on landing your successful contribution! Ping `@Keavon` on Discord to be given the "Code Contributor" role.
|
|
@ -1,21 +1,23 @@
|
|||
+++
|
||||
title = "Student projects"
|
||||
template = "book.html"
|
||||
page_template = "book.html"
|
||||
|
||||
[extra]
|
||||
order = 1 # Page number after chapter intro
|
||||
order = 4 # Chapter number
|
||||
+++
|
||||
|
||||
Graphite offers a number of opportunities for students to contribute by building a self-contained project as part of a structured format. These projects are designed to be completed over several months and are ideal for Google Summer of Code or similar internship programs, solo or group university capstone projects, and other arrangements. Each project has a distinct focus and is a great way to make a meaningful contribution to open source over the length of the program while receiving mentorship and guidance from the Graphite team.
|
||||
|
||||
Student projects require adherence to a set schedule with regular check-ins, milestones, and evaluations. The structured setting is designed to provide a supportive environment for students to learn and grow as developers while gaining real-world industry experience from collaborating on a sizable software product and remaining accountable to stakeholders. It's our goal to make sure you succeed!
|
||||
|
||||
Use this [contributor guide](../..) to start out with the code. Then when you're ready, reach out through [Discord](https://discord.graphite.rs) and use the `#🎓student-projects` channel to discuss and work towards proposing a project with the Graphite core team.
|
||||
Use this [contributor guide](..) to start out with the code. Then when you're ready, reach out through [Discord](https://discord.graphite.rs) and use the `#🎓student-projects` channel to discuss and work towards proposing a project with the Graphite core team.
|
||||
|
||||
## Google Summer of Code
|
||||
|
||||
GSoC is a program offering students a [stipend](https://developers.google.com/open-source/gsoc/help/student-stipends) for successful completion of an internship-style experience with an open source organization. Read about [how it works](https://summerofcode.withgoogle.com/how-it-works/).
|
||||
|
||||
Graphite [participated](https://summerofcode.withgoogle.com/programs/2024/organizations/graphite) in GSoC 2024 and we anticipate doing so again in 2025. Getting involved early is a great way to have a head start and stand out next summer.
|
||||
Graphite [participated in GSoC 2024](https://summerofcode.withgoogle.com/programs/2024/organizations/graphite) and we anticipate doing so again in 2025 if our organization's application is accepted. Getting involved early is a great way to have a head start and stand out in your application.
|
||||
<!-- The proposal formulation period is open now until the April 2 deadline (see the full [timeline](https://developers.google.com/open-source/gsoc/timeline)). -->
|
||||
|
||||
### Writing a proposal
|
||||
|
@ -166,14 +168,13 @@ As is the case with all projects, please discuss this with us on Discord to fles
|
|||
In addition to the detailed projects above, here are some loose ideas that may be expanded into full project descriptions before the 2025 GSoC application period opens:
|
||||
|
||||
- Sophisticated text layout and advanced typography features
|
||||
- PDF import/export? (Scope and viability depend on the state of available libraries)
|
||||
- PDF import/export? (scope and viability depends on the state of available libraries)
|
||||
- Traditional brush engine
|
||||
- [Procedural brush engine](https://github.com/Keavon/Brush-Nodes)
|
||||
- Color management for HDR/WCG (requires good understanding of color science)
|
||||
- Image processing algorithms for photography
|
||||
- Snapping system overhaul
|
||||
- Photo processing graphics algorithms
|
||||
- [Node equivalence rewriting](https://github.com/GraphiteEditor/Graphite/issues/2021)
|
||||
- Snapping system overhaul
|
||||
|
||||
## Successful past projects
|
||||
|
||||
|
@ -196,7 +197,7 @@ Unlike other node editors that are centered around manual graph editing, where u
|
|||
|
||||
While general graph layout algorithms are complex and struggle to produce good results in other node editors, Graphite's graph topology is more constrained and predictable, which makes it possible to design a layout system that can produce good results. Nodes tend to be organized into rows, and layers into columns. This turns the problem into more of a constraint-based, axis-aligned packing problem.
|
||||
|
||||
### 2024: Rendering Performance infrastructure improvements
|
||||
### 2024: Rendering performance infrastructure improvements
|
||||
|
||||
*Graphite performance is bottlenecked by limitations in the new node graph rendering architecture that needs improvements.*
|
||||
|
|
@ -13,35 +13,31 @@
|
|||
#core-team {
|
||||
background-color: var(--color-parchment);
|
||||
|
||||
.block {
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
max-width: Min(100%, 320px);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2 + p {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
h2 + p + p {
|
||||
text-align: center;
|
||||
.handle {
|
||||
margin: 0 0.4em;
|
||||
font-size: 0.5em;
|
||||
font-style: italic;
|
||||
vertical-align: middle;
|
||||
color: var(--color-slate);
|
||||
}
|
||||
|
||||
.emoji {
|
||||
font-size: 0.8em;
|
||||
vertical-align: middle;
|
||||
font-size: 1.5em;
|
||||
margin-left: 0.25em;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.block {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: Min(100%, 480px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -381,6 +381,7 @@ p ~ :is(h1, h2, h3, h4, details summary, blockquote, .image-comparison, .video-b
|
|||
.video-embed + :is(p, .link, .button),
|
||||
p + p > .button,
|
||||
p + :is(.link, section),
|
||||
table td p ~ p,
|
||||
img + .link,
|
||||
article {
|
||||
margin-top: 20px;
|
||||
|
@ -500,6 +501,10 @@ table {
|
|||
th:empty {
|
||||
border: none;
|
||||
}
|
||||
|
||||
:is(h1, h2, h3, h4, h5, h6) + & {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul,
|
||||
|
|
18
website/static/css/balance-text.css
Normal file
18
website/static/css/balance-text.css
Normal file
|
@ -0,0 +1,18 @@
|
|||
.balance-text {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@media (scripting: none) {
|
||||
.balance-text {
|
||||
visibility: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (text-wrap: balance) {
|
||||
.balance-text,
|
||||
.balanced-text {
|
||||
text-align: left;
|
||||
text-wrap: balance;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,3 @@
|
|||
// ========
|
||||
// CAROUSEL
|
||||
// ========
|
||||
|
||||
const FLING_VELOCITY_THRESHOLD = 10;
|
||||
const FLING_VELOCITY_WINDOW_SIZE = 20;
|
||||
|
||||
|
@ -287,95 +283,6 @@ function dragMove(event) {
|
|||
}
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
// ================
|
||||
// IMAGE COMPARISON
|
||||
// ================
|
||||
|
||||
const RECENTER_DELAY = 1;
|
||||
const RECENTER_ANIMATION_DURATION = 0.25;
|
||||
|
||||
window.addEventListener("DOMContentLoaded", initializeImageComparison);
|
||||
|
||||
function initializeImageComparison() {
|
||||
Array.from(document.querySelectorAll("[data-image-comparison]")).forEach((element) => {
|
||||
const moveHandler = (event) => {
|
||||
const factor = (event.clientX - element.getBoundingClientRect().left) / element.getBoundingClientRect().width;
|
||||
const capped = Math.max(0, Math.min(1, factor));
|
||||
|
||||
if (!(element instanceof HTMLElement)) return;
|
||||
element.style.setProperty("--comparison-percent", `${capped * 100}%`);
|
||||
element.dataset.lastInteraction = "";
|
||||
};
|
||||
|
||||
const leaveHandler = (event) => {
|
||||
moveHandler(event);
|
||||
|
||||
const randomCode = Math.random().toString().substring(2);
|
||||
element.dataset.lastInteraction = randomCode;
|
||||
|
||||
setTimeout(() => {
|
||||
if (element.dataset.lastInteraction === randomCode) {
|
||||
element.dataset.recenterStartTime = Date.now();
|
||||
element.dataset.recenterStartValue = parseFloat(element.style.getPropertyValue("--comparison-percent"));
|
||||
|
||||
recenterAnimationStep();
|
||||
}
|
||||
}, RECENTER_DELAY * 1000);
|
||||
};
|
||||
|
||||
const recenterAnimationStep = () => {
|
||||
if (element.dataset.lastInteraction === "") return;
|
||||
|
||||
const completionFactor = (Date.now() - element.dataset.recenterStartTime) / (RECENTER_ANIMATION_DURATION * 1000);
|
||||
if (completionFactor > 1) {
|
||||
element.dataset.lastInteraction = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const factor = smootherstep(completionFactor);
|
||||
const newLocation = lerp(element.dataset.recenterStartValue, 50, factor);
|
||||
element.style.setProperty("--comparison-percent", `${newLocation}%`);
|
||||
|
||||
requestAnimationFrame(recenterAnimationStep);
|
||||
};
|
||||
|
||||
const lerp = (a, b, t) => (1 - t) * a + t * b;
|
||||
const smootherstep = (x) => x * x * x * (x * (x * 6 - 15) + 10);
|
||||
|
||||
element.addEventListener("pointermove", moveHandler);
|
||||
element.addEventListener("pointerenter", moveHandler);
|
||||
element.addEventListener("pointerleave", leaveHandler);
|
||||
element.addEventListener("dragstart", (event) => event.preventDefault());
|
||||
});
|
||||
}
|
||||
|
||||
// ==================
|
||||
// AUTO-PLAYING VIDEO
|
||||
// ==================
|
||||
|
||||
window.addEventListener("DOMContentLoaded", initializeVideoAutoPlay);
|
||||
|
||||
function initializeVideoAutoPlay() {
|
||||
const VISIBILITY_COVERAGE_FRACTION = 0.25;
|
||||
|
||||
const players = document.querySelectorAll("[data-auto-play]");
|
||||
players.forEach((player) => {
|
||||
if (!(player instanceof HTMLVideoElement)) return;
|
||||
|
||||
let loaded = false;
|
||||
|
||||
new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!loaded && entry.intersectionRatio > VISIBILITY_COVERAGE_FRACTION) {
|
||||
player.play();
|
||||
|
||||
loaded = true;
|
||||
};
|
||||
});
|
||||
}, { threshold: VISIBILITY_COVERAGE_FRACTION })
|
||||
.observe(player);
|
||||
});
|
||||
const m = Math; // Keep this line, it fixes a bug in Zola's minifier
|
||||
return m.min(m.max(value, min), max);
|
||||
}
|
57
website/static/js/image-comparison.js
Normal file
57
website/static/js/image-comparison.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
const RECENTER_DELAY = 1;
|
||||
const RECENTER_ANIMATION_DURATION = 0.25;
|
||||
|
||||
window.addEventListener("DOMContentLoaded", initializeImageComparison);
|
||||
|
||||
function initializeImageComparison() {
|
||||
Array.from(document.querySelectorAll("[data-image-comparison]")).forEach((element) => {
|
||||
const moveHandler = (event) => {
|
||||
const factor = (event.clientX - element.getBoundingClientRect().left) / element.getBoundingClientRect().width;
|
||||
const capped = Math.max(0, Math.min(1, factor));
|
||||
|
||||
if (!(element instanceof HTMLElement)) return;
|
||||
element.style.setProperty("--comparison-percent", `${capped * 100}%`);
|
||||
element.dataset.lastInteraction = "";
|
||||
};
|
||||
|
||||
const leaveHandler = (event) => {
|
||||
moveHandler(event);
|
||||
|
||||
const randomCode = Math.random().toString().substring(2);
|
||||
element.dataset.lastInteraction = randomCode;
|
||||
|
||||
setTimeout(() => {
|
||||
if (element.dataset.lastInteraction === randomCode) {
|
||||
element.dataset.recenterStartTime = Date.now();
|
||||
element.dataset.recenterStartValue = parseFloat(element.style.getPropertyValue("--comparison-percent"));
|
||||
|
||||
recenterAnimationStep();
|
||||
}
|
||||
}, RECENTER_DELAY * 1000);
|
||||
};
|
||||
|
||||
const recenterAnimationStep = () => {
|
||||
if (element.dataset.lastInteraction === "") return;
|
||||
|
||||
const completionFactor = (Date.now() - element.dataset.recenterStartTime) / (RECENTER_ANIMATION_DURATION * 1000);
|
||||
if (completionFactor > 1) {
|
||||
element.dataset.lastInteraction = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const factor = smootherstep(completionFactor);
|
||||
const newLocation = lerp(element.dataset.recenterStartValue, 50, factor);
|
||||
element.style.setProperty("--comparison-percent", `${newLocation}%`);
|
||||
|
||||
requestAnimationFrame(recenterAnimationStep);
|
||||
};
|
||||
|
||||
const lerp = (a, b, t) => (1 - t) * a + t * b;
|
||||
const smootherstep = (x) => x * x * x * (x * (x * 6 - 15) + 10);
|
||||
|
||||
element.addEventListener("pointermove", moveHandler);
|
||||
element.addEventListener("pointerenter", moveHandler);
|
||||
element.addEventListener("pointerleave", leaveHandler);
|
||||
element.addEventListener("dragstart", (event) => event.preventDefault());
|
||||
});
|
||||
}
|
21
website/static/js/video-autoplay.js
Normal file
21
website/static/js/video-autoplay.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
const VISIBILITY_COVERAGE_FRACTION = 0.25;
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const players = document.querySelectorAll("[data-auto-play]");
|
||||
players.forEach((player) => {
|
||||
if (!(player instanceof HTMLVideoElement)) return;
|
||||
|
||||
let loaded = false;
|
||||
|
||||
new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!loaded && entry.intersectionRatio > VISIBILITY_COVERAGE_FRACTION) {
|
||||
player.play();
|
||||
|
||||
loaded = true;
|
||||
};
|
||||
});
|
||||
}, { threshold: VISIBILITY_COVERAGE_FRACTION })
|
||||
.observe(player);
|
||||
});
|
||||
});
|
|
@ -13,7 +13,7 @@
|
|||
<section class="reading-material">
|
||||
<div class="block">
|
||||
<div class="details">
|
||||
<h1 class="headline">{{ page.title }}</h2>
|
||||
<h1 class="headline">{{ page.title }}</h1>
|
||||
<span class="publication">By {{ page.extra.author }}. {{ page.date | date(format = "%B %d, %Y", timezone="America/Los_Angeles") }}.</span>
|
||||
<img class="banner" src="{{ page.extra.banner | safe }}" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" />
|
||||
</div>
|
||||
|
@ -38,7 +38,7 @@
|
|||
</div>
|
||||
</section>
|
||||
{%- if not page.summary -%}
|
||||
{{ throw(message = "ARTICLE HAS NO SUMMARY! After the first paragraph (or two short ones), a `<!-- more -->` comment must be inserted in the markdown. Otherwise the blog page will be missing its preview text." | safe) }}
|
||||
{{ throw(message = "ARTICLE HAS NO SUMMARY! After the first paragraph (or two short ones), a `<!-- more -->` comment must be inserted in the markdown. Otherwise the blog page would be missing its preview text." | safe) }}
|
||||
{%- endif -%}
|
||||
{%- endblock content -%}
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
{% if current_path -%}
|
||||
<meta property="og:url" content="https://graphite.rs{{ current_path | safe }}" />
|
||||
{%- endif %}
|
||||
{% if meta_description -%}
|
||||
{%- set meta_description = page.extra.meta_description | default(value = meta_description | default(value = false)) -%}
|
||||
{% if meta_description %}
|
||||
<meta name="description" content="{{ meta_description | safe }}" />
|
||||
<meta property="og:description" content="{{ meta_description | safe }}" />
|
||||
<meta name="twitter:description" content="{{ meta_description | safe }}" />
|
||||
|
@ -31,41 +32,27 @@
|
|||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bona+Nova:wght@700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" />
|
||||
<link rel="stylesheet" href="/base.css" />
|
||||
<link rel="stylesheet" href="/syntax-highlighting.css" />
|
||||
{%- set extra_css_external = page.extra.css_external | default(value = []) | concat(with = css_external | default(value = [])) -%}
|
||||
{% for css_path_external in extra_css_external %}
|
||||
<link rel="stylesheet" href="{{ css_path_external | safe }}" />
|
||||
{%- endfor %}
|
||||
{%- set extra_css = page.extra.css | default(value = []) | concat(with = css | default(value = [])) -%}
|
||||
{% for css_path in extra_css %}
|
||||
{%- set extra_css_external = page.extra.css_external | default(value = []) | concat(with = css_external | default(value = [])) -%}
|
||||
{% for css_path in extra_css | concat(with = extra_css_external) %}
|
||||
<link rel="stylesheet" href="/{{ css_path | safe }}" />
|
||||
{%- endfor %}
|
||||
{%- set extra_js = page.extra.js | default(value = []) | concat(with = js | default(value = [])) -%}
|
||||
{% for js_path in extra_js %}
|
||||
<script src="/js/{{ js_path | safe }}"></script>
|
||||
{%- set extra_css_inline = page.extra.css_inline | default(value = []) | concat(with = css_inline | default(value = [])) -%}
|
||||
{%- set global_css_inline = ["css/balance-text.css"] %}
|
||||
{{ "<" ~ "style>" | safe }}
|
||||
{%- for css_inline in extra_css_inline | concat(with = global_css_inline | default(value = [])) -%}
|
||||
{{ load_data(path = css_inline) | safe }}
|
||||
{%- endfor %}
|
||||
{{ "</" ~ "style>" | safe }}
|
||||
{% set global_js = ["text-justification.js", "navbar.js"] -%}
|
||||
{%- set extra_js = global_js | concat(with = page.extra.js | default(value = []) | concat(with = js | default(value = []))) -%}
|
||||
{% for js_path in extra_js %}
|
||||
{% set script_path = "js/" ~ js_path -%}
|
||||
{{ "<" ~ "script>" | safe }}
|
||||
{{ load_data(path = script_path) | safe }}
|
||||
{{ "</" ~ "script>" | safe }}
|
||||
{%- endfor %}
|
||||
<script src="/js/text-justification.js"></script>
|
||||
<script src="/js/navbar.js"></script>
|
||||
{{- get_env(name = "INDEX_HTML_HEAD_INCLUSION", default = "") | safe }}
|
||||
<style>
|
||||
.balance-text {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@media (scripting: none) {
|
||||
.balance-text {
|
||||
visibility: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (text-wrap: balance) {
|
||||
.balance-text,
|
||||
.balanced-text {
|
||||
text-align: left;
|
||||
text-wrap: balance;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% set latest = articles.pages | slice(end = count) %}
|
||||
{% for article in latest %}
|
||||
<div class="block">
|
||||
<a class="banner" href="{{ article.permalink | safe }}"><img src="{{ article.extra.banner | safe }}" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" /></a>
|
||||
<a class="banner" href="{{ article.permalink | safe }}"><img src="{{ article.extra.banner | safe }}" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Article cover image" /></a>
|
||||
<h2 class="headline"><a href="{{ article.permalink | safe }}">{{ article.title }}</a></h2>
|
||||
<div class="summary">
|
||||
<p>{{ article.summary | striptags | safe }}</p>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue