Skip to content

Commit

Permalink
Merge 4b32ca9 into 048a3f0
Browse files Browse the repository at this point in the history
  • Loading branch information
edyoung authored Dec 18, 2020
2 parents 048a3f0 + 4b32ca9 commit 2b4c926
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 120 deletions.
67 changes: 67 additions & 0 deletions doc/formula_sharing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Formula Sharing Proposal

I'd like to foster community by making it easier for Gnofract 4D users to share things that they have created (or discovered, if you like). This includes:

- Formulas and Coloring Algorithms
- Parameter files
- Possibly image? At least thumbnails, but maybe not high-res renders

## Requirements

- Sharing must be optional. The program can still be used entirely offline
- Sharing must be easy.
- It's more important to making consumption easy than publishing, because more people will consume than publish.

## Proposal

Fractal formulas and parameter files are source code, so they should be stored in a version control system. We're generally using Git, so that seems like the place to start. This is a powerful option,
but not without downsides. Git is not exactly trivial software for unsophisticated users to deal with,
so this is in tension with the goal that sharing should be easy.

### Repository
We designate a central GitHub repository (github.com/fract4d/formulas) as the root.
Within that repo, we establish the layout:

`stdlib/`

contains 'standard' formula files which ship with Gnofract 4D.

`orgform/`

contains saved formula files from Fractint's orgform database

`{username}/`

Within the repo, a folder matching the name of a GitHub user is 'owned' by that user. If user A wants to modify `A/myformula.frm` they should be able to do so without anyone else having to approve. If user B wants to modify `A/myformula.frm` they should be able to submit a PR and have user A review it and choose
whether to merge.

Initially changes can be merged by hand. Later a GitHub Action can take care of this behavior. When a PR is received, it can check that all the files modified are under a directory with the same name as the user who owns the fork it comes from, and auto-merge if so.

### Startup Behavior and Read-Only Sharing

Gnofract 4D will still ship with a snapshot of the 'stdlib' formulas which existed at the time it was compiled, so it can be used without GitHub access.

But it will additionally maintain a local Git repo in a user-selectable directory, by default `~/gnofract4d-formulas`. On startup, if this
directory does not contain a Git repo, user will be prompted "Would you like to download latest formulas?" [Yes/No/Don't ask again]. If yes, we'll run 'git clone' to populate that directory with the current
contents of the shared formula repo. The directory search order will be such that we look in that directory first, before any directory we install, which allows newer versions to supercede the ones we
ship with. We can also display a non-blocking message 'new formulas downloaded!"

A user can edit files stored in ~/.gnofract4d-formulas and we will modify the UX to make it easier to do that directly. TBD: Should the UX 'commit' changes locally, and if so when?

If user has opted in to downloading formulas, on subsequent starts we 'git pull'; in case of a merge conflict we display a warning and do nothing.

### Sharing

To contribute changed formulas back to the mothership, the user will need:
- A github account
- A fork of fract4d/formulas in their own GitHub account
- Create a PR to merge changes from their fork

In an initial version, we could just publish a doc on how to do that. It would be something like:
- Create a GitHub account, if needed
- Fork fract4d/formulas via the GitHub UX
- update the remote reference in their local repo to point to their fork
- Git push
- Create a PR

Later we can have a 'Share' option in the UX which streamlines this behavior.
12 changes: 10 additions & 2 deletions fract4d/c/model/site.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
#include "site.h"
#include "model/stats.h"

IFractalSite::~IFractalSite() {
IFractalSite::~IFractalSite()
{
wait();
}

Expand All @@ -20,6 +21,12 @@ void IFractalSite::wait()
{
if (m_thread.joinable())
{
if (m_thread.get_id() == std::this_thread::get_id())
{
// something is very wrong
std::cerr << this << "Waiting on own thread in IFractalSite::wait()!" << m_thread.get_id() << "\n";
throw std::exception();
}
#ifdef DEBUG_THREADS
std::cerr << this << " : CA : WAIT(" << m_thread.get_id() << ")\n";
#endif
Expand Down Expand Up @@ -120,7 +127,8 @@ void FDSite::interrupt()
interrupted = true;
}

void FDSite::start() {
void FDSite::start()
{
interrupted = false;
}

Expand Down
7 changes: 4 additions & 3 deletions fract4dgui/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

# pylint: disable=no-member


class BrowserDialog(dialog.T):
RESPONSE_REFRESH = 2

Expand Down Expand Up @@ -82,10 +83,10 @@ def onApply(self):
self.model.apply(self.f)

def set_type_cb(self, optmenu):
self.set_type(utils.get_selected(optmenu))
self.set_type(optmenu.get_active())

def on_type_changed(self):
utils.set_selected(self.funcTypeMenu, self.model.current_type)
self.funcTypeMenu.set_active(int(self.model.current_type))
try:
self.set_file(self.f.forms[self.model.current_type].funcFile)
except IndexError:
Expand Down Expand Up @@ -216,7 +217,7 @@ def create_panes(self):
_("Transform Function"),
_("Gradient")])

utils.set_selected(self.funcTypeMenu, self.model.current_type)
self.funcTypeMenu.set_active(int(self.model.current_type))

self.funcTypeMenu.set_tooltip_text(
_("Which formula of the current fractal to change"))
Expand Down
41 changes: 21 additions & 20 deletions fract4dgui/gtkfractal.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,6 @@ def onData(self, fd, condition):

m = messages.parse(t, bytes)

if utils.threads_enabled:
Gdk.threads_enter()

# print "msg: %s %d %d %d %d" % (m,p1,p2,p3,p4)
if t == fract4dc.MESSAGE_TYPE_ITERS:
if not self.skip_updates:
Expand Down Expand Up @@ -223,8 +220,6 @@ def onData(self, fd, condition):
else:
print("Unknown message from fractal thread; %s" % list(bytes))

if utils.threads_enabled:
Gdk.threads_leave()
return True

def __getattr__(self, name):
Expand Down Expand Up @@ -385,7 +380,7 @@ def set_size(self, new_width, new_height):
self.height = new_height

self.image.resize_full(new_width, new_height)
utils.idle_add(self.changed)
GLib.idle_add(self.changed)


# explain our existence to GTK's object system
Expand Down Expand Up @@ -508,14 +503,14 @@ def set_entry():

def set_fractal(entry, event, form, order):
try:
utils.idle_add(
GLib.idle_add(
form.set_param, order, entry.get_text())
except Exception as err:
# FIXME: produces too many errors
msg = "Invalid value '%s': must be a number" % \
entry.get_text()
print(msg)
# utils.idle_add(f.warn,msg)
# GLib.idle_add(f.warn,msg)
return False

set_entry()
Expand All @@ -526,6 +521,7 @@ def set_fractal(entry, event, form, order):
widget.connect('focus-out-event', set_fractal, form, order)

if hasattr(param, "min") and hasattr(param, "max"):
widget.freeze = False
# add a slider
adj = Gtk.Adjustment(
value=0.0,
Expand All @@ -534,12 +530,17 @@ def set_fractal(entry, event, form, order):

def set_adj():
if adj.get_value() != form.params[order]:
widget.freeze = True
adj.set_value(form.params[order])
widget.freeze = False

set_adj()

def adj_changed(adjustment, form, order):
utils.idle_add(
if widget.freeze:
return

GLib.idle_add(
form.set_param, order, adjustment.get_value())

adj.connect('value-changed', adj_changed, form, order)
Expand Down Expand Up @@ -579,11 +580,11 @@ def set_toggle(*args):

def set_fractal(entry, form, order):
try:
utils.idle_add(form.set_param, order, entry.get_active())
GLib.idle_add(form.set_param, order, entry.get_active())
except Exception as err:
msg = "error setting bool param: %s" % str(err)
print(msg)
#utils.idle_add(f.warn, msg)
#GLib.idle_add(f.warn, msg)

return False

Expand Down Expand Up @@ -645,10 +646,10 @@ def set_selected_value(*args):
print(err)
return

utils.set_selected(widget, index)
widget.set_active(index)

def set_fractal(entry, form, order):
new_value = utils.get_selected(widget)
new_value = widget.get_active()
form.set_param(order, new_value)

set_selected_value(self)
Expand Down Expand Up @@ -764,10 +765,10 @@ def set_selected_function():
# print "bad cname"
return

utils.set_selected(widget, index)
widget.set_active(index)

def set_fractal_function(om, f, param, formula):
index = utils.get_selected(om)
index = om.get_active()
if index != -1:
# this shouldn't be necessary but I got weird errors
# trying to reuse the old funclist
Expand Down Expand Up @@ -806,7 +807,7 @@ def set_fractal(*args):
except ValueError as err:
msg = "Invalid value '%s': must be a number" % \
widget.get_text()
utils.idle_add(self.warn, msg)
GLib.idle_add(self.warn, msg)
except Exception as exn:
print(exn)
return False
Expand Down Expand Up @@ -870,17 +871,17 @@ def set_size(self, new_width, new_height):
Hidden.set_size(self, new_width, new_height)
self.widget.set_size_request(new_width, new_height)
except MemoryError as err:
utils.idle_add(self.warn, str(err))
GLib.idle_add(self.warn, str(err))

def draw_image(self, aa=None, auto_deepen=None):
try:
Hidden.draw_image(self, aa, auto_deepen)
except fracttypes.TranslationError as err:
advice = _(
"\nCheck that your compiler settings and formula file are correct.")
utils.idle_add(self.error,
_("Error compiling fractal:"),
err.msg + advice)
GLib.idle_add(self.error,
_("Error compiling fractal:"),
err.msg + advice)
return

def onMotionNotify(self, widget, event):
Expand Down
17 changes: 10 additions & 7 deletions fract4dgui/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
re_ends_with_num = re.compile(r'\d+\Z')
re_cleanup = re.compile(r'[\s\(\)]+')


class Application(Gtk.Application):
def __init__(self, options, userConfig):
super().__init__(application_id="io.github.fract4d")
Expand Down Expand Up @@ -214,7 +215,8 @@ def add_filters(self, chooser):
_("Formula Files"), formula_patterns)
chooser.add_filter(formula_filter)

gradient_patterns = ["*.map", "*.ggr", "*.ugr", "*.cs", "*.pal", "*.ase"]
gradient_patterns = ["*.map", "*.ggr",
"*.ugr", "*.cs", "*.pal", "*.ase"]
gradient_filter = self.get_filter(
_("Gradient Files"), gradient_patterns)
chooser.add_filter(gradient_filter)
Expand Down Expand Up @@ -625,7 +627,7 @@ def get_fourd_actions(self):
("PlanesXWAction", self.set_xw_plane),
("PlanesYZAction", self.set_yz_plane),
("PlanesWYAction", self.set_wy_plane),
]
]

def create_ui(self):
def add_action(name, handler, state=None):
Expand Down Expand Up @@ -662,7 +664,8 @@ def add_action(name, handler, state=None):
# command reference
builder = Gtk.Builder.new_from_file(
os.path.join(this_path, "shortcuts-gnofract4d.ui"))
self.window.set_help_overlay(builder.get_object("shortcuts-gnofract4d"))
self.window.set_help_overlay(
builder.get_object("shortcuts-gnofract4d"))

def director(self, *args):
"""Display the Director (animation) window."""
Expand Down Expand Up @@ -908,13 +911,13 @@ def set_selected_resolution(prefs):
# not found
self.resolutions.append(res)
item = "%dx%d" % (w, h)
utils.add_menu_item(res_menu, item)
res_menu.append_text(item)
index = len(self.resolutions) - 1

utils.set_selected(res_menu, index)
res_menu.set_active(int(index))

def set_resolution(*args):
index = utils.get_selected(res_menu)
index = res_menu.get_active()
if index != -1:
(w, h) = self.resolutions[index]
self.userPrefs.set_size(w, h)
Expand Down Expand Up @@ -1169,7 +1172,7 @@ def paste(self, *args):
text = clipboard.wait_for_text()
if text is None:
return

#print("paste! %s" % text)
grad = self.f.get_gradient()
grad.load_from_url(text)
Expand Down
14 changes: 6 additions & 8 deletions fract4dgui/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import gi

gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
from gi.repository import Gtk, GObject, GLib

from . import dialog, utils

Expand Down Expand Up @@ -116,7 +116,7 @@ def set_prefs(*args):
try:
width = int(entry.get_text())
except ValueError:
Gtk.idle_add(
GLib.idle_add(
self.show_error,
"Invalid value for width: '%s'. Must be an integer" %
entry.get_text())
Expand All @@ -125,7 +125,7 @@ def set_prefs(*args):
if self.fix_ratio.get_active():
height = int(width * float(height) / self.f.width)

utils.idle_add(self.prefs.set_size, width, height)
GLib.idle_add(self.prefs.set_size, width, height)
except Exception as exn:
print(exn)
return False
Expand All @@ -148,7 +148,7 @@ def set_prefs(*args):
try:
height = int(entry.get_text())
except ValueError:
utils.idle_add(
GLib.idle_add(
self.show_error,
"Invalid value for height: '%s'. Must be an integer" %
entry.get_text())
Expand Down Expand Up @@ -405,12 +405,10 @@ def create_antialias_menu(self):
optMenu = utils.create_option_menu(["None", "Fast", "Best"])

def set_widget(*args):
utils.set_selected(
optMenu, self.prefs.getint(
"display", "antialias"))
optMenu.set_active(self.prefs.getint("display", "antialias"))

def set_prefs(*args):
index = utils.get_selected(optMenu)
index = optMenu.get_active()
if index != -1:
self.prefs.set("display", "antialias", str(index))

Expand Down
Loading

0 comments on commit 2b4c926

Please sign in to comment.