Here's how I ended up doing it:
I used an embedded template and passed the current category ID as a variable:
{embed='_products/category_breadcrumb' cat_id='{if triggers:parent_cat_id_1}{triggers:parent_cat_id_1}{if:else}{triggers:cat_id_1}{/if}'}
The Zoo Triggers variable returns blank if the current category has no parent (meaning it's a top-level category), so I had to use a conditional to output the category ID if the current category is top-level.
Then in my embedded template:
{exp:gwcode_categories
cat_id='{embed:cat_id}'
style='linear'
}
{if results_total > 1}
{if cat_count == 1}
<p class="category-filter">
View:
{/if}
{if cat_url_title == triggers:segment_3}
<span class="here">{if depth == 1}All{/if}{if depth == 2}{cat_name}{/if}</span>
{/if}
{if cat_url_title != triggers:segment_3}
<span><a href="{path='products/category/{cat_url_title}'}">{if depth == 1}All{/if}{if depth == 2}{cat_name}{/if}</a></span>
{/if}
{if cat_count == results_total}
</p>
{/if}
{/if}
{/exp:gwcode_categories}
The caveat is that it only works with a 2-level category tree, but that's all I need for my specific situation.
I'm using the Zoo Triggers {triggers:parent_cat_id_1}
and {triggers:cat_id_1}
variables, but you could do the same thing with Low Seg2Cat {segment_1_category_parent_id}
and {segment_1_category_id}
variables.
I also used an embedded template to get the parse order right, but I imagine you could use Stash for this too.