Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
twnlink committed Mar 9, 2023
0 parents commit 993adee
Show file tree
Hide file tree
Showing 6 changed files with 515 additions and 0 deletions.
174 changes: 174 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Created by https://www.toptal.com/developers/gitignore/api/ruby,rubymine
# Edit at https://www.toptal.com/developers/gitignore?templates=ruby,rubymine

### Ruby ###
*.gem
*.rbc
/.config
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/spec/examples.txt
/test/tmp/
/test/version_tmp/
/tmp/

# Used by dotenv library to load environment variables.
# .env

# Ignore Byebug command history file.
.byebug_history

## Specific to RubyMotion:
.dat*
.repl_history
build/
*.bridgesupport
build-iPhoneOS/
build-iPhoneSimulator/

## Specific to RubyMotion (use of CocoaPods):
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
# vendor/Pods/

## Documentation cache and generated files:
/.yardoc/
/_yardoc/
/doc/
/rdoc/

## Environment normalization:
/.bundle/
/vendor/bundle
/lib/bundler/man/

# for a library or gem, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# Gemfile.lock
# .ruby-version
# .ruby-gemset

# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc

# Used by RuboCop. Remote config files pulled in from inherit_from directive.
# .rubocop-https?--*

### RubyMine ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf

# AWS User-specific
.idea/**/aws.xml

# Generated files
.idea/**/contentModel.xml

# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml

# Gradle
.idea/**/gradle.xml
.idea/**/libraries

# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr

# CMake
cmake-build-*/

# Mongo Explorer plugin
.idea/**/mongoSettings.xml

# File-based project format
*.iws

# IntelliJ
out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Cursive Clojure plugin
.idea/replstate.xml

# SonarLint plugin
.idea/sonarlint/

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

# Editor-based Rest Client
.idea/httpRequests

# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

### RubyMine Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721

# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr

# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/

# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml

# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/

# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$

# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml

# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml

# End of https://www.toptal.com/developers/gitignore/api/ruby,rubymine
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# decent
decent is a text user interface (TUI) library written in Ruby without the use of Curses or any external libraries.

decent is modeled after SolidJS's Signals (Observables), and a React-like workflow.

```ruby
require "decent"

Decent::App.new do
count = ref 0

text derived { "Count is: #{count.value}!" }

# NOTE: We'll have interactions later! This is just a demo :)
Thread.new do
count.value += 1
end
end
```
153 changes: 153 additions & 0 deletions decent.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
require "./reactivity.rb"
require "./terminal_renderer.rb"
require "observer"
require "io/console"

module Decent
class App
include Decent

def initialize(renderer = nil, &tui)
@renderer = renderer || TerminalRenderer.new(STDOUT)

@tree = { type: "root", children: [] }
@tree[:parent] = @tree

@current_node = @tree

instance_eval(&tui)
end

def element(name, properties = {}, &children)
subscriptions = []
node = { type: name, properties: properties, children: [], parent: @current_node, subscriptions: subscriptions }

properties.each do |key, prop|
if prop.is_a?(Ref)
properties[key] = prop.value

effect = Watcher.new do |new|
properties[key] = new
render
end

prop.add_observer effect
subscriptions << effect
end
end

@current_node[:children] << node
@current_node = node
children&.call
@current_node = @current_node[:parent]

node
end

def show(args, &children)
# ruby does not like argument names being keywords, so we do this hack to make it work
cond = args[:if]

# this has cond: as a property to trigger a rerender when the value changes
node = element("show", { cond: cond })

should_show = false

effect do
if should_show != cond.value
should_show = cond.value

if cond.value
@current_node = node

children&.call
else
node[:children] = []
end
end
end
end

# this currently triggers a render twice upon initialization.
# this is because we do not have explicit dependency arrays and call `render` inside of the effect body.
# TODO: make this not shit
def each(args, &child)
of = args[:of]
node = element("each")

effect do
node[:children] = []

of.value.each_with_index do |item, index|
@current_node = node

child.call item, index
end

render
end
end

def box(width: "auto", height: "auto", &children)
element("box", { width: width, height: height, }, &children)
end

def text(text)
element("text", { content: text })
end

def render
@renderer.clear
# these go in the order of starting coordinates (x, y), then the max amount rows and cols
@bounds = [[1, 0], @renderer.size]

stack = [@tree]

until stack.length == 0
node = stack.pop

# todo: this is a hack until i get rendering to work in the correct order
stack.concat node[:children].reverse

properties = node[:properties]


# todo: nothing here actually resets the bounds when we leave the component. not good.
case node[:type]
when "text"
@renderer.draw properties[:content], @bounds[0][0], @bounds[0][1]

# todo: remove this shit later, layouting should be handled by stacks and flows, not by the components themselves
# also, the .length + 1 shit is just to add a space, not *really* necessary
@bounds[0][0] += properties[:content].length + 1
when "box"
box = ""

width = properties[:width] == "auto" ? @bounds[1][0] : properties[:width]
height = properties[:height] == "auto" ? @bounds[1][1] : properties[:height]

box << "┌#{'─' * (width - 2)}\n"
box << "│#{' ' * (width - 2)}\n" * (height - 2)
box << "└#{'─' * (width - 2)}┘"

@renderer.draw box, @bounds[0][0], @bounds[0][1]

@bounds[0][0] += 1
@bounds[0][1] += 1
@bounds[1][0] -= 2
@bounds[1][1] -= 2
end
end
end

def run
begin
@renderer.setup
render
loop {}
ensure
@renderer.cleanup
end
end
end
end
20 changes: 20 additions & 0 deletions demo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require "./decent.rb"

app = Decent::App.new do
count = ref 1

box do
box do
text derived { "Count is: #{count.value}! Also, decent is fucking sweet. Look at these sick-ass boxes."}
end
end

Thread.new do
loop do
count.value += 1
sleep 0.07
end
end
end

app.run
Loading

0 comments on commit 993adee

Please sign in to comment.