Skip to content

Instantly share code, notes, and snippets.

@Utsira
Created July 12, 2015 15:41
Show Gist options
  • Save Utsira/5db6e0eccb68c70d3670 to your computer and use it in GitHub Desktop.
Save Utsira/5db6e0eccb68c70d3670 to your computer and use it in GitHub Desktop.
Markdown Codea with paragraph stitching
--# Main
-- Markdown Codea
-- paragraph-stitching version
-- by Yojimbo2000
displayMode(OVERLAY)
displayMode(FULLSCREEN)
function setup()
state = Stitch
state.init()
end
function draw()
background(130, 158, 189, 255)
state.draw()
end
function touched(t)
state.touched(t)
end
function orientationChanged()
state.orientation()
end
--# Stitch
Stitch = {}
function Stitch.init()
para = 1
trackY = HEIGHT * 0.6
paragraphs = {}
paragraphs[1] = Paragraph(para, trackY)
trackY = trackY - (paragraphs[1].img.height * 0.5)
scrollY = trackY
parameter.watch("para")
fontSize(20)
Notify{
delay = 2,
recurring = 4,
pos = vec2(WIDTH*0.5, HEIGHT * 0.25),
condition = function() return not endOfText end,
img = Markdown{
width = WIDTH * 0.4,
palette = "dark",
paragraph = [[## Touch the lower half of the screen to move down]]
}
}
Notify {
delay = 6,
pos = vec2(WIDTH*0.5, HEIGHT * 0.75),
img = Markdown{
width = WIDTH * 0.4,
palette = "dark",
paragraph = [[## Touch the top half of the screen to move back up]]
}
}
end
function Stitch.draw()
pushMatrix()
scrollY = scrollY + (trackY - scrollY) * 0.1
translate(0, -scrollY+HEIGHT*0.3)
for i,v in ipairs(paragraphs) do
if v.kill then
table.remove(paragraphs, i)
else
v:draw()
end
end
popMatrix()
Notify.updateAll()
end
function Stitch.touched(t)
if t.state==ENDED then
Notify.touchAll()
if t.y < HEIGHT * 0.5 then
para = para + 1
-- local lastPara = #paragraphs --remember the last non-new para (to animate sliding up)
if para>#paragraphs then --see if there's a new para
local new = Paragraph(para, trackY)
if new.kill then --no more paragraphs
para = para - 1
endOfText = true
return
end
paragraphs[#paragraphs+1] = new
else
trackY = trackY - (paragraphs[para-1].img.height * 0.5)
end
paragraphs[para-1]:fade(128) --fade animations (paragraph focus)
paragraphs[para]:fade(255)
-- local h = (paragraphs[para].img.height + paragraphs[para-1].img.height)*0.5
trackY = trackY - (paragraphs[para].img.height * 0.5)
--[[
for i=1,lastPara do
paragraphs[i]:fade(255-(math.abs(i-para)*40))
end
]]
elseif para>1 then
local h = (paragraphs[para].img.height + paragraphs[para-1].img.height)*0.5
trackY = trackY + h
para = para - 1
endOfText = false
--[[
for i,v in ipairs(paragraphs) do
paragraphs[i]:fade(255-(math.abs(i-para)*40))
end
]]
paragraphs[para]:fade(255)
paragraphs[para+1]:fade(128)
end
end
end
function Stitch.orientation()
for i,v in ipairs(paragraphs) do
v:setText() --rerender the text
v.m.texture = v.img
v.w = v.img.width --reset width/ height
v.h = v.img.height
if i>1 then
local u = paragraphs[i-1]
v.y = u.y - ((v.h + u.h) * 0.5) --recalculate y position
end
end
trackY = paragraphs[para].y - paragraphs[para].h * 0.5 --recalculate focus/ insertion point. This is sometimes slightly off.
end
--# Paragraph
Paragraph = class()
local size = 25
function Paragraph:init(para)
self.text = parseParagraph(testString, para)
if not self.text then self.kill=true return end
self:setText()
self.m = mesh()
self.m.texture = self.img
self.m:addRect(0,0,0,0)
trackY = trackY - (self.img.height * 0.5)
self.y = trackY
self.w, self.h = self.img.width * 0.5, self.img.height * 0.5
self.alpha = 0
self.targetAlpha = 255
tween(1, self, {w = self.img.width, h=self.img.height}, tween.easing.sineOut)
end
function Paragraph:setText()
fontSize(size) --use the regular fontsize command to set the size of the body level text
self.img = Markdown{
-- debug = true, --debug print
palette = "solarized",
width = WIDTH *0.8, --wrap width
paragraph = self.text
}
end
function Paragraph:draw()
self.alpha = self.alpha + (self.targetAlpha - self.alpha) * 0.1
self.m:setRect(1,WIDTH*0.5, self.y, self.w, self.h)
self.m:setRectColor(1, 255, 255, 255, self.alpha)
self.m:draw()
end
function Paragraph:fade(alpha)
self.targetAlpha = alpha
end
--# Notify
Notify = class()
local notifications = {} --store notifications
function Notify:init(t)
local del = t.delay or 0
self.timer = ElapsedTime + del --time until notification should appear
self.pos = t.pos or (vec2(WIDTH, HEIGHT)*0.5)
self.img = t.img --the only mandatory input for this class, the image that will be displayed
self.alpha = 0
self.targetAlpha = 255
self.recurring = t.recurring --set this if the notification should reoccur, set to seconds delay for subsequent notifications
self.condition = t.condition --condition which must be met for recurrance
self.duration = t.duration --seconds until auto dismissal. omit this if notification should stay until a touch event
notifications[#notifications+1] = self
end
function Notify:update()
if self.condition and not self.condition() then return end
if ElapsedTime > self.timer then
self.alpha = self.alpha + (self.targetAlpha - self.alpha) * 0.1 --fade in or out
tint(255, self.alpha)
sprite(self.img, self.pos.x, self.pos.y, self.img.width)
tint()
if self.targetAlpha == 0 and self.alpha<0.1 then --if fade-out has completed
if self.recurring then
-- if self.condition() then
self.timer = ElapsedTime + self.recurring --reset ready for next fade-in
self.recurring = self.recurring * 2 --delay gradually gets longer
self.targetAlpha = 255
-- end
else
self.kill = true
end
end
if self.duration and ElapsedTime > self.timer + self.duration then
self.targetAlpha = 0 --auto-dismissal of notification (if this is set.)
end
end
end
function Notify:touched()
if ElapsedTime > self.timer + 0.5 then --split second delay
self.targetAlpha = 0 --before dismssal
end
end
--functions below are not part of the class
function Notify.updateAll()
for i,v in ipairs(notifications) do
if v.kill then
table.remove(notifications, i)
else
v:update()
end
end
end
function Notify.touchAll()
for i,v in ipairs(notifications) do
v:touched()
end
end
--# Markdown
--markdown-esque string formatting
local style = { --simple cascading styles
palette = {
solarized = {color(214, 212, 203, 255),
color(91, 73, 51, 255),
color(95, 89, 135, 255),
color(89, 78, 66, 255),
color(133, 97, 72, 255)
},
dark = {color(28, 41, 60, 200),
color(210, 208, 181, 255),
color(0, 255, 225, 255),
color(223, 202, 159, 255),
color(215, 223, 171, 255)
}
},
body = { --body format
font = "IowanOldStyle", --"Baskerville", --"Optima",
col = 2,
},
heading = { --the array part of style.heading contains styles for heading1, heading2, etc (overrides global)
{--font = "HelveticaNeueUltraLight",
size=2.5}, --Heading1. Size is proportion of body size
{size=1.5}, --Heading2
{size=1.2}, --Heading 3
all = { --global headings settings
font = "Avenir", --"HelveticaNeueLight",
col = 3,
}
},
block = { --block quotes
size = 0.9,
font = "Optima", --"Verdana", -- "HelveticaNeue",
col = 4, },
bullet = { --bullet points
size = 0.9,
col = 5,
font = "Optima"
},
--non-standard names for font families. add entries here if family doesnt conform to suffix pattern of "[no suffix]" for regular, "-Italic" for italic, "-Bold", "-BoldItalic", (eg if the family has, say, "-Roman" for regular, "-Black" for bold, "-Oblique" for italic etc)
Palatino = {
regular = "Palatino-Roman"
},
HoeflerText = {
regular = "HoeflerText-Regular",
bold = "HoeflerText-Black",
boldItalic = "HoeflerText-BlackItalic"
},
Optima = {
regular = "Optima-Regular"
},
HelveticaNeueUltraLight = {
regular = "HelveticaNeue-UltraLight",
italic = "HelveticaNeue-UltraLightItalic",
bold = "HelveticaNeue-Light",
boldItalic = "HelveticaNeue-LightItalic"
},
HelveticaNeueLight = {
regular = "HelveticaNeue-Light",
italic = "HelveticaNeue-LightItalic",
bold = "HelveticaNeue",
boldItalic = "HelveticaNeue-Italic"
},
IowanOldStyle = {
regular = "IowanOldStyle-Roman"
},
Avenir = {
regular = "Avenir-Roman",
italic = "Avenir-Oblique",
bold = "Avenir-Heavy",
boldItalic = "Avenir-HeavyOblique"
}
}
--font("Didot-Bold")
local face --name of base font currently being used
local size --size of base font
local palette --color palette in use
function parseParagraph(tex, paraNo)
tex = tex.."\n\n"
local paraCount = 0
for paragraph in string.gmatch(tex, "(.-)\n\n") do
paraCount = paraCount + 1
if paraCount == paraNo then
return paragraph
end
end
end
function Markdown(t)
local paragraph = t.paragraph
pushStyle()
local _, baseHeight = textSize("dummy") --defines paragraph separation
size = fontSize() --base size of body text
palette = style.palette[t.palette] --color scheme in use
local gutter = size * 0.5 --border around the text
--PRE-PROCESS TYPOGRAPHY
paragraph = string.gsub(paragraph, "(%S+)'", "%1\u{2019}") --right single quote. Do this first in order to catch apostrophes
paragraph = string.gsub(paragraph, "'(%S+)", "\u{2018}%1") --left single quote
paragraph = string.gsub(paragraph, "%-%-%-", "\u{2014}") --em-dash
paragraph = string.gsub(paragraph, "%-%-", "\u{2013}") --en-dash
paragraph = string.gsub(paragraph, "\"(%S+)", "\u{201C}%1") --left double quote
paragraph = string.gsub(paragraph, "(%S+)\"", "%1\u{201D}") --right double quote
--RESET TO DEFAULT BODY FONT FOR NEW PARAGRAPH
style.set(style.body)
local indent = gutter
local cursorSet = false --set to true once initial cursor position for paragraph is set according to font size, paragraph separation
--CHECK ELEMENTS THAT ONLY COME AT START OF PARAGRAPH
--BLOCK
local bl
paragraph, bl = string.gsub(paragraph, "^> ", "", 1)
if bl>0 then
indent = gutter + (size * 3) --indent paragraph
style.set(style.block)
end
--HEADINGS
local hBegin, hEnd = string.find(paragraph, "^%#+") --look for number of hashes at start of para
if hBegin then
local headLevel = hEnd + 1 - hBegin
paragraph = string.gsub(paragraph, "^%#+%s?", "")
style.set(style.heading.all) --global heading settings
style.set(style.heading[headLevel]) --level specific settings
end
textMode(CORNER)
textWrapWidth(t.width - (indent - gutter)) --use built in wrapping to
local _, height = textSize(paragraph) --guestimate height of paragraph
local img = image(t.width+(gutter*2), height+(gutter*2))
setContext(img)
local back = t.background or palette[1]
background(back)
textWrapWidth(0) --we need to turn off text wrapping and implement our own because the built-in wrapping does not give us control over the point at which the text starts (first line indentation), nor tell us where the last line ends.
local cursor = vec2(indent,height+gutter)
local italic = false
local bold = false
paragraph = paragraph.."\n" --add return (this also allows final part of line to be captured)
for subpara in string.gmatch(paragraph, "(.-)\n") do --for tight-nested lists
subpara = subpara.." "
--BULLETS
local bu
subpara, bu = string.gsub(subpara, "^%- ", "", 1)
if bu>0 then
style.set(style.bullet)
local _, h = textSize("dummy")
cursor.y = cursor.y - h
text("\u{2022}", gutter + (size * 1.75), cursor.y)
cursorSet = true
indent = gutter + (size * 3)
cursor.x = indent
end
--PARSE WORDS
for element, control in string.gmatch(subpara, "(.-)([%s*]+)") do -- "(.-)(%*+%s+)" separate at white space, *
if string.find(control, "%s") then --if whitespace
element = element.." " --put spaces back in
end
local w,h = textSize(element) --find size of word
if t.debug then print(element,control) end --debug print
if not cursorSet then --place first line of paragraph (paragraph separation etc)
cursor.y = cursor.y - h
cursorSet = true
end
--WRAPPING
if cursor.x + w > t.width then --if word will take us over edge
cursor.x = indent --carriage return
cursor.y = cursor.y - h
end
text(element, cursor.x, cursor.y) --print word
cursor.x = cursor.x + w
--BOLD AND ITALICS
local eBegin, eEnd = string.find(control, "%*+") --count number of asterisks
if eBegin then
local emph = eEnd + 1 - eBegin
if emph==3 then
bold = not bold
italic = not italic
elseif emph==2 then
bold = not bold
else
italic = not italic
end
if bold and italic then
font(style.boldItalic(face))
elseif bold then
font(style.bold(face))
elseif italic then
font(style.italic(face))
else
style.font(face)
end
end
end --of word
end --of subpara
setContext()
popStyle()
return img
end
function style.set(sty)
for func, val in pairs(sty) do --set font features for whatever keys are in the style table
style[func](val)
end
end
--3 functions to handle non-standard named fonts (eg -Black for bold, -Oblique for italc etc)
function style.bold(f)
if style[f] and style[f].bold then --check if a nonstandard bold face is specified
return style[f].bold
end
return f.."-Bold" --else just append bold to family name
end
function style.italic(f)
if style[f] and style[f].italic then
return style[f].italic
end
return f.."-Italic"
end
function style.boldItalic(f)
if style[f] and style[f].boldItalic then
return style[f].boldItalic
end
return f.."-BoldItalic"
end
--the function names below correspond to the bottom level keys in the style table, eg font, col, size
function style.font(f)
face = f
if style[face] and style[face].regular then
font(style[face].regular)
else
font(face)
end
end
function style.col(col)
fill(palette[col])
end
function style.size(s)
fontSize(size * s)
end
--# Sampletext
testString = [[
# *Markdown*-like text formatting --- in **Codea!**
Have you ever wanted an easy way to format text --- adding *italic*, **bold**, ***bold-italic,*** different type faces, font sizes and colours, indented block quotes, plus typography features such as "smart quotes" and em-dashes, all of them nestable within one-another --- on the fly?
## Well now you can, with **Markdown Codea.**
> *Try switching the orientation of your device to test the hand-made text wrapping feature! Touch the screen to scroll the text*
### "But --- *what **is** this **Markdown**?!?*" I hear you yell.
Markdown is a way of adding rich formatting, such as:
- *Emphasis*
- **Strong emphasis**
- *Really, **really** strong emphasis*
- Or **really, *really* strong emphasis** if you prefer
- Block quotes, different headings...
- Oh, and ***bullet points!*** Bullet points can be displayed in a tight list like this, by only separating each item with one return
Or, if you prefer, you can have:
- A loose list
- Of bullet points
- Just separate each bullet with two returns
And it's all done using plain text. So it's great for using in plain-text environments such as code editors. As *Markdown's* creator John Gruber said:
> The overriding design goal for *Markdown's* formatting syntax is to make it as **readable** as possible. The idea is that a *Markdown*-formatted document should be publishable **as-is, as plain text, *without* looking like it's been marked up with tags** or formatting instructions.
**But the best thing about Markdown is --- *you already know how to use it***. It's used on lots of forums, including *Codea Talk.* I've thrown in some nice, *Pandoc*-inspired extras such as typographer's quotes for the apostrophe and for 'single quotation marks' and "double" quote marks, plus en--dash and em---dash
]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment