Skip to content

Instantly share code, notes, and snippets.

@tshirtman
Last active April 6, 2019 02:36
Show Gist options
  • Save tshirtman/d1750e17c7cf268ff58536f3f7afbf2c to your computer and use it in GitHub Desktop.
Save tshirtman/d1750e17c7cf268ff58536f3f7afbf2c to your computer and use it in GitHub Desktop.
# coding: utf8
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.scatter import Scatter
from kivy.properties import ObjectProperty, ListProperty, \
StringProperty, NumericProperty
from kivy.lang import Builder
from kivy.factory import Factory
from json import load, dump
from os.path import join
import logging
logging.basicConfig()
KV = '''
#:import split os.path.split
<Picture>
size: img.size
Image:
id: img
source: root.source
size: self.texture_size
<Text>:
size: text.size
Label:
id: text
text: root.text
color: root.color
size: self.texture_size
font_size: root.font_size
<SourceSelector>:
title: 'select image source'
size_hint: .5, .5
BoxLayout:
orientation: 'vertical'
FileChooserIconView:
id: fc
BoxLayout:
size_hint_y: None
height: '48dp'
Button:
text: 'cancel'
on_release: root.dismiss()
Button:
text: 'ok'
disabled: not fc.selection
on_release: root.save(fc.selection)
<TextEditor>:
title: 'edit text'
size_hint: .5, .5
BoxLayout:
orientation: 'vertical'
TextInput:
id: ti
text: root.target.text
GridLayout:
cols: 2
Label:
text: 'font size'
Slider:
id: font_size
min: 5
max: 200
value: root.target.font_size
Label:
text: 'font size'
Slider:
id: font_color_r
min: 0
max: 1
value: root.target.color[0]
Label:
text: 'font size'
Slider:
id: font_color_g
min: 0
max: 1
value: root.target.color[1]
Label:
text: 'font size'
Slider:
id: font_color_b
min: 0
max: 1
value: root.target.color[2]
Label:
text: 'font size'
Slider:
id: font_color_a
min: 0
max: 1
value: root.target.color[3]
BoxLayout:
size_hint_y: None
height: '48dp'
Button:
text: 'cancel'
on_release: root.dismiss()
Button:
text: 'ok'
on_release: root.save()
<FilePopup>:
title: 'export as…'
size_hint: .5, .5
BoxLayout:
orientation: 'vertical'
FileChooserIconView:
id: fc
path: app.last_path
on_selection: ti.text = split(self.selection[0])[-1]
TextInput:
id: ti
pos_hint: {'center_y': .5, 'center_x': .5}
text: app.out_name
multiline: False
size_hint_y: None
height: self.minimum_height
on_text_validate:
root.action(fc, ti.text)
root.dismiss()
BoxLayout:
size_hint_y: None
height: '48dp'
Button:
text: 'cancel'
on_release:
root.dismiss()
Button:
text: 'ok'
on_release:
root.action(fc, ti.text)
root.dismiss()
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint_y: None
height: '48dp'
Button:
text: 'new text'
on_release:
app.add_text()
Button:
text: 'new image'
on_release:
app.add_image()
Button:
text: 'save'
on_release:
app.save()
Button:
text: 'load'
on_release:
app.load()
Button:
text: 'export'
on_release:
app.export()
Widget:
id: canvas
'''
class WYSIWYGObject(Scatter):
save_properties = []
def config(self):
return (
self.__class__.__name__,
{
x: getattr(self, x)
for x in self.save_properties
}
)
class Picture(WYSIWYGObject):
source = StringProperty()
save_properties = ['source', 'pos', 'scale', 'rotation']
def on_touch_up(self, touch):
if self.collide_point(*touch.pos) and touch.is_double_tap:
self.select_source()
return super(Picture, self).on_touch_up(touch)
def select_source(self):
SourceSelector(target=self).open()
class Text(WYSIWYGObject):
text = StringProperty()
color = ListProperty([1, 1, 1, 1])
font_size = NumericProperty(20)
save_properties = ['text', 'color', 'font_size', 'pos', 'rotation']
def on_touch_up(self, touch):
if self.collide_point(*touch.pos) and touch.is_double_tap:
self.edit()
return super(Text, self).on_touch_up(touch)
def edit(self):
TextEditor(target=self).open()
class SourceSelector(Popup):
target = ObjectProperty()
def save(self, source):
self.target.source = source[0]
self.dismiss()
class TextEditor(Popup):
target = ObjectProperty()
def save(self):
self.target.text = self.ids.ti.text
self.target.font_size = self.ids.font_size.value
self.target.color = [
self.ids.font_color_r.value,
self.ids.font_color_g.value,
self.ids.font_color_b.value,
self.ids.font_color_a.value,
]
self.dismiss()
class FilePopup(Popup):
action = ObjectProperty()
class WYSIWYG(App):
out_name = StringProperty()
last_path = StringProperty()
def build(self):
return Builder.load_string(KV)
def load(self):
FilePopup(action=self._load).open()
def _load(self, filechooser, filename):
self.last_path = filechooser.path
canvas = self.root.ids.canvas
canvas.clear_widgets()
filename = join(filechooser.path, filename)
with open(filename) as f:
try:
configs = load(f)
except Exception:
logging.exception('Unable to load file (corrupted?)')
return
for c in configs:
cls, conf = c
w = Factory.get(cls)(**conf)
canvas.add_widget(w)
def save(self):
FilePopup(action=self._save).open()
def _save(self, filechooser, filename):
self.last_path = filechooser.path
with open(filename, 'w') as f:
widgets = reversed(self.root.ids.canvas.children)
dump([w.config() for w in widgets], f)
def export(self):
FilePopup(action=self._export).open()
def _export(self, filechooser, filename):
self.last_path = filechooser.path
if not filename.endswith('.png'):
filename += '.png'
self.out_name = filename
self.root.ids.canvas.export_to_png(filename)
def add_text(self):
w = Text()
self.root.ids.canvas.add_widget(w)
w.edit()
def add_image(self):
w = Picture()
self.root.ids.canvas.add_widget(w)
w.select_source()
if __name__ == '__main__':
WYSIWYG().run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment