9 Commits

Author SHA1 Message Date
8c3232c788 Adds cover page 2025-08-31 14:02:46 -06:00
f3eed964f0 Merge pull request 'fix namespacing with CSS' (#3) from phajas/smartbar:phajas/fix_namespacing into master
Reviewed-on: buzzert/smartbar#3
2025-08-31 18:53:23 +00:00
Peter Hajas
896a67ca8a fix namespacing with CSS 2025-08-31 12:46:36 -06:00
0fb28a1df8 Merge pull request '[New Article] Linux GTK Apps: A Language Comparison' (#1) from zanneth/smartbar:zanneth/gtkapplang into master
Reviewed-on: buzzert/smartbar#1
2025-08-31 18:36:34 +00:00
Charles Magahern
7b55b65a34 gtkapplang: Increase margin 2025-08-31 11:22:37 -07:00
Charles Magahern
cef6b7597b gtkapplang: Pages 3 and 4, final edits
Conflicts:
	pages.yaml
2025-08-31 11:22:34 -07:00
Charles Magahern
1afb4d5fa6 gtkapplang: First two pages
Conflicts:
	pages.yaml
2025-08-31 11:22:12 -07:00
a2d5b8cb44 Merge pull request '[New Article] Driving Miss MUNI' (#2) from zanneth/smartbar:zanneth/driving-miss-muni into master
Reviewed-on: buzzert/smartbar#2
2025-08-31 18:21:02 +00:00
Charles Magahern
613591b6b6 drivingmissmuni: First draft 2025-08-24 19:50:25 -07:00
22 changed files with 535 additions and 28 deletions

60
assets/css/gtkapplang.css Normal file
View File

@@ -0,0 +1,60 @@
#article-header {
background-color: #0d0d0d;
color: white;
text-align: center;
padding: 5pt 0 5pt 0;
}
#article-header > div {
display: inline-block;
vertical-align: middle;
margin: 5pt;
}
img#gtk-logo {
width: 50px;
}
#article-body {
columns: 2;
font: 8.75pt/1.40 Helvetica, sans-serif;
padding: 0.25in 0.5in 0 0.5in;
}
#article-body p {
text-indent: 2em;
}
#article-body p:first-child {
margin-top: 0;
text-indent: 0;
}
/*
span.first-word {
font-size: xx-large;
font-weight: bold;
line-height: 0;
}
*/
h1, h2, h3 {
margin: 2pt;
text-align: left;
font-family: Helvetica, sans-serif;
}
h3 {
font-weight: normal;
font-style: italic;
}
.code {
font-family: monospace;
}
.endmark {
text-align: center;
margin-top: 1em;
color: #555;
}

View File

@@ -1,3 +1,17 @@
#page {
@font-face {
font-family: "Heading Now";
src: url("/assets/font/HeadingNow-95Medium.otf");
}
.ytmnd-1 {
color: #fff;
text-shadow:
0px 10px 0 rgb(209, 1, 209),
0px 20px 0 black;
}
.ytmnd-2 {
color: #fff;
text-shadow:
0px 6px 0 rgb(209, 1, 209);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/font/munifice.ttf Normal file

Binary file not shown.

Binary file not shown.

BIN
assets/img/cover.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

BIN
assets/img/logo-gtk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -8,6 +8,7 @@ import (
"io/fs"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
@@ -26,6 +27,152 @@ func floatToIn(v float64) string {
return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.4fin", v), "0"), ".")
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// extractAndScopeCSS extracts <style> blocks from HTML content and scopes them to a page-specific selector
func extractAndScopeCSS(content string, pageID string) (string, string) {
styleRegex := regexp.MustCompile(`(?s)<style[^>]*>(.*?)</style>`)
matches := styleRegex.FindAllStringSubmatch(content, -1)
var scopedCSS strings.Builder
contentWithoutCSS := content
// Remove all <style> blocks from content
contentWithoutCSS = styleRegex.ReplaceAllString(contentWithoutCSS, "")
// Process each CSS block and scope it
for _, match := range matches {
cssContent := strings.TrimSpace(match[1])
if cssContent == "" {
continue
}
// Scope the CSS rules to the page
scopedCSS.WriteString(scopeCSS(cssContent, pageID))
scopedCSS.WriteString("\n\n")
}
return contentWithoutCSS, scopedCSS.String()
}
// scopeCSS wraps CSS rules with a page-specific selector
func scopeCSS(css string, pageID string) string {
// Simple CSS scoping - wrap all rules with the page selector
lines := strings.Split(css, "\n")
var scopedLines []string
var inRule bool
var ruleBuffer strings.Builder
for _, line := range lines {
trimmed := strings.TrimSpace(line)
// Skip comments and empty lines
if trimmed == "" || strings.HasPrefix(trimmed, "/*") {
scopedLines = append(scopedLines, line)
continue
}
// Handle @-rules (like @font-face, @media) - don't scope these
if strings.HasPrefix(trimmed, "@") {
if inRule {
// Finish current rule first
scopedLines = append(scopedLines, scopeRuleBuffer(ruleBuffer.String(), pageID))
ruleBuffer.Reset()
inRule = false
}
scopedLines = append(scopedLines, line)
continue
}
// Detect start of CSS rule
if strings.Contains(trimmed, "{") && !inRule {
inRule = true
ruleBuffer.WriteString(line)
ruleBuffer.WriteString("\n")
} else if inRule {
ruleBuffer.WriteString(line)
ruleBuffer.WriteString("\n")
// Check if rule ends
if strings.Contains(trimmed, "}") {
scopedLines = append(scopedLines, scopeRuleBuffer(ruleBuffer.String(), pageID))
ruleBuffer.Reset()
inRule = false
}
} else {
// Standalone line - might be a selector
if strings.Contains(trimmed, "{") {
ruleBuffer.WriteString(line)
ruleBuffer.WriteString("\n")
inRule = true
} else {
scopedLines = append(scopedLines, line)
}
}
}
// Handle any remaining rule
if inRule && ruleBuffer.Len() > 0 {
scopedLines = append(scopedLines, scopeRuleBuffer(ruleBuffer.String(), pageID))
}
return strings.Join(scopedLines, "\n")
}
func scopeRuleBuffer(rule string, pageID string) string {
lines := strings.Split(strings.TrimSpace(rule), "\n")
if len(lines) == 0 {
return rule
}
// Find the selector line (first line before {)
for i, line := range lines {
if strings.Contains(line, "{") {
// Extract selector part
parts := strings.Split(line, "{")
if len(parts) >= 2 {
selector := strings.TrimSpace(parts[0])
rest := "{" + strings.Join(parts[1:], "{")
// Scope the selector
scopedSelector := scopeSelector(selector, pageID)
lines[i] = scopedSelector + " " + rest
}
break
}
}
return strings.Join(lines, "\n")
}
func scopeSelector(selector string, pageID string) string {
// Split multiple selectors by comma
selectors := strings.Split(selector, ",")
var scopedSelectors []string
for _, sel := range selectors {
sel = strings.TrimSpace(sel)
if sel == "" {
continue
}
// Don't scope selectors that already include the page ID or are body/html
if strings.Contains(sel, "#"+pageID) || sel == "body" || sel == "html" {
scopedSelectors = append(scopedSelectors, sel)
} else {
// Scope to page
scopedSelectors = append(scopedSelectors, "#"+pageID+" "+sel)
}
}
return strings.Join(scopedSelectors, ", ")
}
type PageData struct {
Title string
Content template.HTML
@@ -38,6 +185,8 @@ type IndexPage struct {
type PrintPage struct {
Content template.HTML
PageID string
ScopedCSS template.HTML
}
type TwoUpSheet struct {
@@ -164,15 +313,47 @@ func main() {
printTplPath := filepath.Join(templatesDir, "print.gohtml")
printTpl := template.Must(template.New("print").Parse(mustReadFile(printTplPath)))
var printPages []PrintPage
var allScopedCSS strings.Builder
for _, pagePath := range pageFiles {
content := strings.TrimSpace(mustReadFile(pagePath))
printPages = append(printPages, PrintPage{Content: template.HTML(content)})
// Generate unique page ID based on filename
base := filepath.Base(pagePath)
pageID := strings.TrimSuffix(base, filepath.Ext(base)) + "-page"
// Extract and scope CSS
contentWithoutCSS, scopedCSS := extractAndScopeCSS(content, pageID)
// Collect all scoped CSS
if scopedCSS != "" {
allScopedCSS.WriteString(scopedCSS)
allScopedCSS.WriteString("\n")
}
printPages = append(printPages, PrintPage{
Content: template.HTML(contentWithoutCSS),
PageID: pageID,
ScopedCSS: template.HTML(scopedCSS),
})
}
var outPrint bytes.Buffer
if err := printTpl.Execute(&outPrint, map[string]any{"Pages": printPages, "PageSizeAttr": pageSizeStyleAttr(config.PageWidthIn, config.PageHeightIn)}); err != nil {
if err := printTpl.Execute(&outPrint, map[string]any{
"Pages": printPages,
"PageSizeAttr": pageSizeStyleAttr(config.PageWidthIn, config.PageHeightIn),
}); err != nil {
panic(err)
}
if err := writeFile(filepath.Join(outDir, "print.html"), outPrint.Bytes()); err != nil {
// Inject scoped CSS into the output
cssToInject := allScopedCSS.String()
finalOutput := strings.ReplaceAll(
outPrint.String(),
"PLACEHOLDER_FOR_SCOPED_CSS",
cssToInject,
)
if err := writeFile(filepath.Join(outDir, "print.html"), []byte(finalOutput)); err != nil {
panic(err)
}
@@ -190,10 +371,20 @@ func main() {
print2TplPath := filepath.Join(templatesDir, "print_2up.gohtml")
print2Tpl := template.Must(template.New("print2up").Parse(mustReadFile(print2TplPath)))
var outPrint2 bytes.Buffer
if err := print2Tpl.Execute(&outPrint2, map[string]any{"Sheets": sheets}); err != nil {
if err := print2Tpl.Execute(&outPrint2, map[string]any{
"Sheets": sheets,
}); err != nil {
panic(err)
}
if err := writeFile(filepath.Join(outDir, "print_2up.html"), outPrint2.Bytes()); err != nil {
// Inject scoped CSS into the 2-up output
finalOutput2Up := strings.ReplaceAll(
outPrint2.String(),
"PLACEHOLDER_FOR_SCOPED_CSS",
cssToInject,
)
if err := writeFile(filepath.Join(outDir, "print_2up.html"), []byte(finalOutput2Up)); err != nil {
panic(err)
}
}

View File

@@ -1,5 +1,9 @@
# locations in pages/
- cover.html
- bouba.html
- gtkapplang-1.html
- gtkapplang-2.html
- gtkapplang-3.html
- gtkapplang-4.html
- drivingmissmuni.html

View File

@@ -1,9 +0,0 @@
<style>
#bouba-page {
background-color: red;
}
</style>
<div id="bouba-page" class="page-base">
This is a test kiki.
</div>

View File

@@ -1,9 +1,49 @@
<style>
#cover-page {
background-color: green;
position: relative;
background:
linear-gradient(rgba(0,0,0,0.35), rgba(0,0,0,0.35)),
url("/assets/img/cover.jpg") no-repeat center center;
background-size: cover;
}
#logo {
position: absolute;
top: 29px;
left: 8px;
transform: scaleY(1.85);
font-family: "Heading Now";
font-size: 68px;
color: #fff;
text-shadow:
0px 10px 0 rgb(209, 1, 209),
0px 20px 0 black;
}
#edition {
position: absolute;
right: 8px;
bottom: 20px;
transform: scaleY(0.8);
font-family: "Heading Now";
font-size: 32px;
text-align: right;
}
</style>
<div id="cover-page" class="page-base">
welcome to smart bar
<div id="logo" class="ytmnd-1">smart bar</div>
<div id="edition" class="ytmnd-2">
<div>sf cyberculture zine</div>
<div>issue 1, 2025</div>
</div>
</div>

View File

@@ -0,0 +1,97 @@
<style>
#missmunipage {
background-image: url("assets/img/driving-miss-muni.png");
background-size: cover;
color: white;
padding: 0.5in;
font-family: "Kenyan Coffee Regular", sans-serif;
font-size: 13pt;
}
@font-face {
font-family: "Munificent";
src: url("assets/font/munifice.ttf");
}
@font-face {
font-family: "Kenyan Coffee Regular";
src: url("assets/font/kenyan-coffee-rg.otf");
}
@font-face {
font-family: "TS Block Bold";
src: url("assets/font/ts-block-bold.ttf");
}
@font-face {
font-family: "COMPUTER Robot";
src: url("assets/font/computer-robot.ttf");
}
@font-face {
font-family: "Birds of Paradise";
src: url("assets/font/birds-of-paradise.ttf");
}
h1 {
font-family: Munificent;
font-size: 42pt;
color: #ff3232;
margin: 0;
}
h2 {
font-family: monospace;
font-size: 13pt;
font-style: italic;
font-weight: bold;
margin-top: 0;
color: #f0f0f0;
}
p {
margin: 0;
text-indent: 2em;
}
span.muni {
font-family: Munificent;
color: #ff3232;
}
span.cyberwheel {
font-family: "TS Block Bold";
}
span.computer {
font-family: "COMPUTER Robot", monospace;
}
span.fancy {
font-family: "Birds of Paradise", cursive;
}
</style>
<div id="missmunipage" class="page-base">
<div id="header">
<h1>DRIVING MISS MUNI</h1>
<h2>by Bram Noidz</h2>
</div>
<div id="article-body">
<p>Your boy Bram gets into a lot of arguments with people about transportation. You know this guy loves talking about getting from Point A to Point B. In fact these days I rarely care about where Point A and Point B are even located, especially since we're talking about coordinates in <span class="computer">Meatspace</span> after all. If the Internet is collectively known as the <em>Information Superhighway,</em> what does that make actual highways? Just lame <em>informationless substandard highways</em> I guess.</p>
<p>I'll be the first to admit: I use The Bus sometimes. Every once in a while, I gotta go somewhere, and it happens to line up with a <span class="muni">MUNI</span> route. What can I say? I can't always be scooting around on my <span class="cyberwheel">Cyberwheel</span>. Sometimes it needs charging, and I need some quality smartphone time.</p>
<p>The other day it occurred to me what an obscene, extravagant luxury <span class="fancy">The Bus</span> actually is. Despite this, a lot of my friends tell me that its supposedly an economical way to get around. Are you kidding me? You're literally being driven around in a stretch limousine with a chauffeur. It's even fancier than those <span class="computer">ROBOTAXIS</span> that are driving around because you get the upscale human touch. When your chauffeur is blasting the horn at the crossing bicyclist, you know it's coming from the heart.</p>
<p>"But it's cheap," you might be thinking. Give me a break. You don't think Bram's done his <span class="computer">Deep Research</span> on this one? While the sticker price on the fare might only be <strong>$2.75</strong>, Uncle Sam is subsidizing a vast majority of the total cost, which is actually about <strong>$13.25 per ride.</strong> <span class="muni">MUNI</span> only recovers about 17% of operating expenses from fare revenue. The rest has gotta come from somewhere! And those silly little Cable Cars cost even more to run, but we like how kitschy and quaint they are even though their brakes are made out of wood and look retarded.</p>
<p>So the next time you're trying to get somewhere in The City, go ahead and splurge and take The Bus. Your <span class="fancy">Luxury Limousine</span> is just one stop away.</p>
</div>
</div>

35
pages/gtkapplang-1.html Normal file
View File

@@ -0,0 +1,35 @@
<link rel="stylesheet" href="assets/css/gtkapplang.css" />
<div id="body" class="page-base">
<div id="article-header">
<div>
<img id="gtk-logo" src="assets/img/logo-gtk.png" />
</div>
<div>
<h1>Linux GTK Apps</h1>
<h2>A Language Comparison</h2>
<h3>by P. Michael Cho</h3>
</div>
</div>
<div id="article-body">
<p><span class="first-word">A</span> famous hacker once said, "Linux is only free if your time has no value." Well, anyone who knows me knows that my time is about as worthless as a bag of salt. Thus, I really had nothing to lose when I decided to ditch the glitzy, glamorous commercial OS'es and go all-in on Linux this year.</p>
<p>It's really not that bad in 2025. Maybe it really is the <em>Year of the Linux Desktop.</em> A lot of stuff just works out of the box. You don't have to waste a whole weekend getting sound to work in YouTube videos. X11 isn't really a thing anymore, so fiddling with ancient hieroglyphic <strong>Xorg config files</strong> is no longer required just to get your anime girlfriend wallpaper desktop to show up on the screen.</p>
<p>Emboldened with a new sense of optimism for the Linux desktop, I decided to try writing a few <strong>native apps</strong> to see what the developer ecosystem is like. <strong>GTK,</strong> formerly known as the <strong>GIMP ToolKit,</strong> appears to be the de-facto widget toolkit for creating native GUI apps on Linux, so I decided to learn and explore how to develop apps using this toolkit.</p>
<p>The first problem I encountered was a severe sense of language paralysis. There are many, many choices of programming languages when developing GTK apps, which isn't necessarily a good thing. Apple doesn't always do everything right, but upholding Swift as the one true language for developing macOS and iOS apps reduces a lot of fragmentation for developers. A walled garden keeps the snakes out! Nevertheless, I decided to try and write a few small apps in a variety of different languages to see which one feels the best.</p>
<h4>C (Rating: B-)</h4>
<p>The first language I decided to try is an oldie-but-goodie. Just plain ol' C. Linux guys really like C, and really hate pretty much every other language, and the GTK toolkit itself is implemented in C, so it seemed like a good first choice.</p>
<p>One cool thing about GTK is that it is based on runtime library called <strong>GLib,</strong> which implements a bunch of object-oriented design patterns in C. It defines an object model, lifetime semantics, and a bunch of commonly used data structures that you can use in your app. Object-oriented programming is definitely out of style nowadays but I still think it is a solid paradigm for developing user interfaces. It makes sense conceptually for all widgets to inherit from a base class that defines things like screen geometry and parent-child relationships.</p>
<p>My first couple of hours with C were glorious. The code was just falling out of my hands. The language is so simple, it's nearly impossible to waste any time on design.</p>
</div>
</div>

25
pages/gtkapplang-2.html Normal file
View File

@@ -0,0 +1,25 @@
<link rel="stylesheet" href="assets/css/gtkapplang.css" />
<div id="body" class="page-base">
<div id="article-body">
<p>Unfortunately this honeymoon phase quickly came to an end. Writing in C requires a <strong>lot</strong> of boilerplate code, and significant use of <strong>macros,</strong> against which I am generally ideologically opposed. For every "class" type that you define in your app, you have to implement several functions: one that initializes the class itself with the runtime, another for handling dynamic setting of properties, another to handle getting those properties, one for initializing instances of that class, and one for destroying or "finalizing" instances of that class. Confusingly there are two different functions for destroying objects, one called <strong>finalize</strong> and another called <strong>dispose,</strong> and I guess this was done to eliminate cycles in the reference counting mechanism implemented by GObject. Big yuck.</p>
<p>I eventually did finish the app I was writing in pure C, but it was a shitload of code. There are probably a couple of memory leaks too. One great thing about writing in C is that compiling the code is basically instantaneous. I actually started to feel angry and upset thinking about how much time we waste compiling code in newer languages. Do people know that complex C programs take only a few seconds to compile? Debugging was excellent too. GDB just works and I can see everything. Overall a pretty good experience I would say.</p>
<h4>C++ (Rating: C-)</h4>
<p>Linux guys are going to hate me for even mentioning C++, but I of course had to give it a try. Despite its numerous flaws, it's still the best bang for your buck when it comes to offering reasonably good object-oriented features with very fast performance.</p>
<p>For using GLib and GTK in C++ I decided to use the <strong>gtkmm</strong> library, which implements a variety of C++ bindings for all of the classes in the toolkit. Along with that I also used <strong>glibmm</strong> and <strong>giomm</strong> which are required to use the GLib and GIO dependencies in C++ code.</p>
<p>I was immediately disappointed to learn that a ton of boilerplate was still required just for defining some basic data types with properties. Properties are an important abstraction in GObject that let you bind data to various widgets, so this isn't something that can be easily avoided. Basically for each data member in your class, you need <strong>three accessor functions</strong>: two that 'get' and 'set' the data itself, and a third that returns a reference to the <span class="code">Glib::Property</span> object representing that member.</p>
<p>Another disappointment was the documentation for gtkmm and glibmm. Just atrocious. Looks like autogenerated Doxygen slop. Come on guys, it's not 2008 anymore.</p>
<p>One positive thing I will say about using C++ is that memory management is a lot more tolerable than C. Using the <span class="code">Glib::RefPtr</span> smart pointer class instead of manually managing GObject lifecycles is a blessing.</p>
<p>I did end up finishing this project as well and the end result was satisfactory. Overall I didn't really have a lot of fun writing C++, but then again, who does.</p>
</div>
</div>

29
pages/gtkapplang-3.html Normal file
View File

@@ -0,0 +1,29 @@
<link rel="stylesheet" href="assets/css/gtkapplang.css" />
<div id="body" class="page-base">
<div id="article-body">
<h4>Rust (Rating: C+)</h4>
<p>Of course I cannot go without mentioning the fad language of the decade: <strong>Rust.</strong> I was actually pretty excited to try using Rust to write a GTK app. The users and designers of the language alike are very opinionated, Rust programs are usually very high quality, and I generally like the overall design of the language and the standard library.</p>
<p>I decided to use the <strong>gtkrs</strong> crate for my Rust app, which was trivial to setup using Rust's excellent build system. From what I can tell, a majority of the code in gtkrs is automatically generated, so there isn't a lot of manual wrapping done, which is quite nice.</p>
<p>First impressions were very positive. Just like with C++, memory management was straightforward and conventional. I didn't waste a lot of time allocating objects and passing them around the library. The designers of gtkrs did a really good job in creating the bindings while also making them memory safe, which is very much concordant with the Rust philosophy.</p>
<p>Significantly less boilerplate was required with Rust as well, thanks to macros provided by the <span class="code">glib</span> crate like <span class="code">property</span> and <span class="code">object_subclass</span>. Getter and setter functions are optional and mostly for convenience.</p>
<p>So why did I give Rust a <strong>C+</strong> rating? The main reason is because Rust is not an object-oriented language. It feels really unnatural to shoehorn object-oriented design patterns into Rust when it was clearly designed not to support that. Of course, it <em>kinda works,</em> and you can finish your app with some pretty nice code to maintain, but it feels sort of like writing classical poetry in LaTeX.</p>
<p>I finished writing my app in Rust and felt pretty good about it, but to be honest I did have to rewrite large parts of it multiple times. It took me a few tries to figure out how to make the compiler happy while still keeping things "Rusty" if you'll pardon the phrase.</p>
<h4>Vala (Rating: B)</h4>
<p>The last language I tried was one I've never heard of before, and probably you haven't either. <strong>Vala</strong> is an object-oriented language that is built by the GNOME team and sits on top of the GLib runtime. It has the standard set of nice-to-have features in modern languages such as async/await, generics, and type inference. It also has a few features that are specifically designed to interact nicely with the GObject runtime, such as signals and properties.</p>
<p>Another cool thing about Vala is that all of your code gets cross-compiled to decently human-readable C code. So there isn't a lot of worry about cross-platform support, debuggability, or performance, at least compared to C.</p>
<p>Vala has by far the least amount of boilerplate of all the languages I tried. Because it natively supports properties, your data classes and widget subclasses are extremely minimal and easy to understand.</p>
</div>
</div>

17
pages/gtkapplang-4.html Normal file
View File

@@ -0,0 +1,17 @@
<link rel="stylesheet" href="assets/css/gtkapplang.css" />
<div id="body" class="page-base">
<div id="article-body">
<p>Documentation for Vala and its libraries is pretty good too. All of it is hosted at the <strong>Valadoc</strong> website which is really easy to search and has a nice layout. I can tell that quite a lot of it is autogenerated from the C documentation, so sometimes it's a bit awkward to read the parts written in prose, but overall not bad.</p>
<p>It's quite easy to create Vala bindings to C libraries, especially if those C libraries are also implemented using the GLib runtime and use GObjects. You create these little files called <strong>VAPI</strong> files and the compiler does most of the work for you. By far one of the easiest FFI's I've ever used.</p>
<p>I think the only thing I really don't like about Vala is that the language itself doesn't really <em>spark joy.</em> For language connoisseurs like myself, it just feels kind of <em>meh.</em> From what I can tell that was actually one of the goals of the language. The designers didn't really set out to push the boundaries of programming language theory with Vala, and instead just wanted a simple language that makes it easier to write apps. I respect that.</p>
<p>Another weird and perhaps surprising disappointment is that every LLM I tried is very poor at generating Vala code. There is actually quite a lot of Vala code out on the Internet, and so statistically speaking it seems likely that it would be part of some training data. My theory is that because a lot of these Linux guys still have a beef with Microsoft, they refuse to upload a lot of code to Github which is the primary website from where code gets scraped. If someone trains an LLM with all the code on these little Gitlab instances, I'm excited to try it.</p>
<p>Overall I found Vala very pleasant to use. I think if the community hivemind were to select a language as its primary language for GTK development, it would be Vala. Just make sure to brush up on your hands-on-the-keyboard coding skills, since you won't be able to <em>vibe-code</em> your way out of some problems with Vala. <span class="endmark"></span></p>
</div>
</div>

View File

@@ -13,15 +13,17 @@
@media print {
html, body { width: auto !important; height: auto !important; overflow: visible !important; margin: 0 !important; padding: 0 !important; background: white !important; }
}
/* Each #page is a fixed-size sheet */
#page { width: var(--page-w, 5.5in); height: var(--page-h, 8.5in); background: white; box-shadow: none !important; overflow: hidden; }
#page { page-break-after: always; break-after: page; }
#page:last-child { page-break-after: auto; break-after: auto; }
/* Each .page-container is a fixed-size sheet */
.page-container { width: var(--page-w, 5.5in); height: var(--page-h, 8.5in); background: white; box-shadow: none !important; overflow: hidden; }
.page-container { page-break-after: always; break-after: page; }
.page-container:last-child { page-break-after: auto; break-after: auto; }
PLACEHOLDER_FOR_SCOPED_CSS
</style>
</head>
<body {{ .PageSizeAttr }}>
{{ range .Pages }}
<div id="page">{{ .Content }}</div>
<div class="page-container" id="{{ .PageID }}">{{ .Content }}</div>
{{ end }}
</body>
</html>

View File

@@ -14,16 +14,18 @@
/* Each .sheet is one letter-sized page */
.sheet { display: flex; gap: 0; margin: 0; padding: 0; width: 11in; height: 8.5in; }
.cell { width: 5.5in; height: 8.5in; overflow: hidden; }
.cell > #page { width: 5.5in; height: 8.5in; box-shadow: none !important; }
.cell > .page-content { width: 5.5in; height: 8.5in; box-shadow: none !important; }
.sheet { page-break-after: always; break-after: page; }
.sheet:last-child { page-break-after: auto; break-after: auto; }
PLACEHOLDER_FOR_SCOPED_CSS
</style>
</head>
<body>
{{ range .Sheets }}
<div class="sheet">
<div class="cell"><div id="page">{{ .Left.Content }}</div></div>
{{ if .Right }}<div class="cell"><div id="page">{{ .Right.Content }}</div></div>{{ end }}
<div class="cell"><div class="page-content" id="{{ .Left.PageID }}">{{ .Left.Content }}</div></div>
{{ if .Right }}<div class="cell"><div class="page-content" id="{{ .Right.PageID }}">{{ .Right.Content }}</div></div>{{ end }}
</div>
{{ end }}
</body>